a3s_code_core/agent_api/session_options.rs
1//! Session option builder interface.
2//!
3//! `SessionOptions` is the host-facing capability configuration for a session.
4//! Keeping the builder implementation here lets `agent_api.rs` keep the type
5//! shape visible while moving option construction behavior behind this module.
6
7use super::SessionOptions;
8use crate::prompts::{PlanningMode, SystemPromptSlots};
9use crate::queue::SessionQueueConfig;
10use crate::subagent::WorkerAgentSpec;
11use a3s_memory::MemoryStore;
12use std::path::PathBuf;
13use std::sync::Arc;
14
15impl std::fmt::Debug for SessionOptions {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 f.debug_struct("SessionOptions")
18 .field("model", &self.model)
19 .field("task_priority", &self.task_priority)
20 .field("agent_dirs", &self.agent_dirs)
21 .field("worker_agents", &self.worker_agents.len())
22 .field("skill_dirs", &self.skill_dirs)
23 .field("queue_config", &self.queue_config)
24 .field("search_config", &self.search_config)
25 .field("security_provider", &self.security_provider.is_some())
26 .field("llm_client", &self.llm_client.is_some())
27 .field("context_providers", &self.context_providers.len())
28 .field("cognitive_context", &self.cognitive_context)
29 .field("confirmation_manager", &self.confirmation_manager.is_some())
30 .field("permission_checker", &self.permission_checker.is_some())
31 .field("permission_policy", &self.permission_policy.is_some())
32 .field("planning_mode", &self.planning_mode)
33 .field("goal_tracking", &self.goal_tracking)
34 .field(
35 "skill_registry",
36 &self
37 .skill_registry
38 .as_ref()
39 .map(|r| format!("{} skills", r.len())),
40 )
41 .field(
42 "enforce_active_skill_tool_restrictions",
43 &self.enforce_active_skill_tool_restrictions,
44 )
45 .field("memory_store", &self.memory_store.is_some())
46 .field("durable_memory", &self.durable_memory)
47 .field("memory_observers", &self.memory_observers.len())
48 .field("memory_maintenance", &self.memory_maintenance)
49 .field("file_memory_dir", &self.file_memory_dir)
50 .field("session_store", &self.session_store.is_some())
51 .field(
52 "session_checkpoint_export_sink",
53 &self.session_checkpoint_export_sink.is_some(),
54 )
55 .field("file_session_store_dir", &self.file_session_store_dir)
56 .field("session_id", &self.session_id)
57 .field("rl_trajectory", &self.rl_trajectory)
58 .field("llm_logprobs", &self.llm_logprobs)
59 .field("llm_top_logprobs", &self.llm_top_logprobs)
60 .field("auto_save", &self.auto_save)
61 .field("artifact_store_limits", &self.artifact_store_limits)
62 .field("immutable_content_adapter", &self.immutable_content_adapter)
63 .field(
64 "tool_result_transform_policy",
65 &self.tool_result_transform_policy,
66 )
67 .field("tool_presentation_profile", &self.tool_presentation_profile)
68 .field("max_parse_retries", &self.max_parse_retries)
69 .field("tool_timeout_ms", &self.tool_timeout_ms)
70 .field("llm_api_timeout_ms", &self.llm_api_timeout_ms)
71 .field("circuit_breaker_threshold", &self.circuit_breaker_threshold)
72 .field(
73 "duplicate_tool_call_threshold",
74 &self.duplicate_tool_call_threshold,
75 )
76 .field("sandbox_handle", &self.sandbox_handle.is_some())
77 .field("workspace_services", &self.workspace_services.is_some())
78 .field("workspace_retrieval", &self.workspace_retrieval)
79 .field("auto_compact", &self.auto_compact)
80 .field("auto_compact_threshold", &self.auto_compact_threshold)
81 .field("max_context_tokens", &self.max_context_tokens)
82 .field("continuation_enabled", &self.continuation_enabled)
83 .field("max_continuation_turns", &self.max_continuation_turns)
84 .field("mcp_manager", &self.mcp_manager.is_some())
85 .field("temperature", &self.temperature)
86 .field("thinking_budget", &self.thinking_budget)
87 .field("max_tool_rounds", &self.max_tool_rounds)
88 .field("max_parallel_tasks", &self.max_parallel_tasks)
89 .field("auto_delegation", &self.auto_delegation)
90 .field("manual_delegation_enabled", &self.manual_delegation_enabled)
91 .field("auto_parallel_delegation", &self.auto_parallel_delegation)
92 .field("prompt_slots", &self.prompt_slots.is_some())
93 .finish()
94 }
95}
96
97impl SessionOptions {
98 pub fn new() -> Self {
99 Self::default()
100 }
101
102 pub fn with_model(mut self, model: impl Into<String>) -> Self {
103 self.model = Some(model.into());
104 self
105 }
106
107 /// Set the priority of top-level work submitted by this session.
108 pub fn with_task_priority(mut self, priority: crate::task_scheduler::TaskPriority) -> Self {
109 self.task_priority = priority;
110 self
111 }
112
113 pub fn with_agent_dir(mut self, dir: impl Into<PathBuf>) -> Self {
114 self.agent_dirs.push(dir.into());
115 self
116 }
117
118 /// Register a cattle-style worker with this session's task delegation registry.
119 pub fn with_worker_agent(mut self, spec: WorkerAgentSpec) -> Self {
120 self.worker_agents.push(spec);
121 self
122 }
123
124 /// Register multiple cattle-style workers with this session.
125 pub fn with_worker_agents<I>(mut self, specs: I) -> Self
126 where
127 I: IntoIterator<Item = WorkerAgentSpec>,
128 {
129 self.worker_agents.extend(specs);
130 self
131 }
132
133 pub fn with_queue_config(mut self, config: SessionQueueConfig) -> Self {
134 self.queue_config = Some(config);
135 self
136 }
137
138 /// Override the agent-level web-search configuration for this session.
139 ///
140 /// The configuration is value-typed and therefore safe to pass through
141 /// the Node, Python, and Go SDK boundaries. `None` keeps the agent/global
142 /// configuration unchanged.
143 pub fn with_search_config(mut self, config: crate::config::SearchConfig) -> Self {
144 self.search_config = Some(config);
145 self
146 }
147
148 /// Enable default security provider with taint tracking and output sanitization
149 pub fn with_default_security(mut self) -> Self {
150 self.security_provider = Some(Arc::new(crate::security::DefaultSecurityProvider::new()));
151 self
152 }
153
154 /// Set a custom security provider
155 pub fn with_security_provider(
156 mut self,
157 provider: Arc<dyn crate::security::SecurityProvider>,
158 ) -> Self {
159 self.security_provider = Some(provider);
160 self
161 }
162
163 /// Provide a custom LLM client for this session.
164 ///
165 /// When set, this client is used directly, overriding the `provider/model`
166 /// factory resolution. Use it to plug in a provider the built-in factory
167 /// does not cover, a deterministic record/replay client for tests, or an
168 /// HTTP-layer proxy/audit wrapper. Mirrors [`Self::with_workspace_backend`];
169 /// the `provider/model` config path remains the default when unset.
170 pub fn with_llm_client(mut self, client: Arc<dyn crate::llm::LlmClient>) -> Self {
171 self.llm_client = Some(client);
172 self
173 }
174
175 /// Add a file system context provider for simple RAG
176 pub fn with_fs_context(mut self, root_path: impl Into<PathBuf>) -> Self {
177 let config = crate::context::FileSystemContextConfig::new(root_path);
178 self.context_providers
179 .push(Arc::new(crate::context::FileSystemContextProvider::new(
180 config,
181 )));
182 self
183 }
184
185 /// Add a custom context provider
186 pub fn with_context_provider(
187 mut self,
188 provider: Arc<dyn crate::context::ContextProvider>,
189 ) -> Self {
190 self.context_providers.push(provider);
191 self
192 }
193
194 /// Bind this session to one exact A3S Use cognitive-package generation.
195 ///
196 /// The supplied runtime value contains a serializable immutable binding
197 /// and a host-owned provider. On restart the host must inject the same
198 /// binding again; Code never resolves `latest`, opens a package path, or
199 /// substitutes graph/personal-memory context.
200 pub fn with_cognitive_context(
201 mut self,
202 context: crate::cognitive_context::CognitiveContextSession,
203 ) -> Self {
204 self.cognitive_context = Some(context);
205 self
206 }
207
208 /// Set a confirmation manager for HITL
209 pub fn with_confirmation_manager(
210 mut self,
211 manager: Arc<dyn crate::hitl::ConfirmationProvider>,
212 ) -> Self {
213 self.confirmation_manager = Some(manager);
214 self
215 }
216
217 /// Set a confirmation policy for HITL
218 ///
219 /// The policy will be used to create a ConfirmationManager when the session is built.
220 /// This is the preferred way to configure HITL from the Node SDK.
221 pub fn with_confirmation_policy(mut self, policy: crate::hitl::ConfirmationPolicy) -> Self {
222 self.confirmation_policy = Some(policy);
223 self
224 }
225
226 /// Set a serializable permission policy for tool execution.
227 pub fn with_permission_policy(mut self, policy: crate::permissions::PermissionPolicy) -> Self {
228 self.permission_checker = Some(Arc::new(policy.clone()));
229 self.permission_policy = Some(policy);
230 self
231 }
232
233 /// Set a permission checker
234 pub fn with_permission_checker(
235 mut self,
236 checker: Arc<dyn crate::permissions::PermissionChecker>,
237 ) -> Self {
238 self.permission_checker = Some(checker);
239 self
240 }
241
242 /// Set planning mode
243 pub fn with_planning_mode(mut self, mode: PlanningMode) -> Self {
244 self.planning_mode = mode;
245 self
246 }
247
248 /// Enable planning (shortcut for `with_planning_mode(PlanningMode::Enabled)`)
249 pub fn with_planning(mut self, enabled: bool) -> Self {
250 self.planning_mode = if enabled {
251 PlanningMode::Enabled
252 } else {
253 PlanningMode::Disabled
254 };
255 self
256 }
257
258 /// Enable goal tracking
259 pub fn with_goal_tracking(mut self, enabled: bool) -> Self {
260 self.goal_tracking = enabled;
261 self
262 }
263
264 /// Add the compatibility built-in skill registry.
265 ///
266 /// A3S Code no longer ships embedded built-in skills, so this currently
267 /// installs an empty registry. Use skill directories, inline skills, or a
268 /// custom skill registry for reusable behavior.
269 pub fn with_builtin_skills(mut self) -> Self {
270 self.skill_registry = Some(Arc::new(crate::skills::SkillRegistry::with_builtins()));
271 self
272 }
273
274 /// Add a custom skill registry
275 pub fn with_skill_registry(mut self, registry: Arc<crate::skills::SkillRegistry>) -> Self {
276 self.skill_registry = Some(registry);
277 self
278 }
279
280 /// Enable or disable legacy global active-skill `allowed-tools` restrictions.
281 ///
282 /// The default is disabled: active skills do not block ordinary session
283 /// tools before the host permission/HITL approval chain runs.
284 pub fn with_active_skill_tool_restrictions(mut self, enabled: bool) -> Self {
285 self.enforce_active_skill_tool_restrictions = Some(enabled);
286 self
287 }
288
289 /// Add skill directories to scan for skill files (*.md).
290 /// Merged with any global `skill_dirs` from
291 /// [`CodeConfig`](crate::config::CodeConfig) at session build time.
292 pub fn with_skill_dirs(mut self, dirs: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
293 self.skill_dirs.extend(dirs.into_iter().map(Into::into));
294 self
295 }
296
297 /// Load skills from a directory (eager — scans immediately into a registry).
298 pub fn with_skills_from_dir(mut self, dir: impl AsRef<std::path::Path>) -> Self {
299 let registry = self
300 .skill_registry
301 .unwrap_or_else(|| Arc::new(crate::skills::SkillRegistry::new()));
302 if let Err(e) = registry.load_from_dir(&dir) {
303 tracing::warn!(
304 dir = %dir.as_ref().display(),
305 error = %e,
306 "Failed to load skills from directory — continuing without them"
307 );
308 }
309 self.skill_registry = Some(registry);
310 self
311 }
312
313 /// Set a custom memory store override.
314 ///
315 /// Sessions resolve a default memory store when no override is provided.
316 pub fn with_memory(mut self, store: Arc<dyn MemoryStore>) -> Self {
317 self.memory_store = Some(store);
318 self.file_memory_dir = None;
319 self
320 }
321
322 /// Install an exact, typed durable-memory repository binding.
323 ///
324 /// The binding selects either candidate-only shadowing or bounded
325 /// active-only recall. Its live repository is runtime-only, while its
326 /// secret-free typed identity is persisted; hosts restoring a session must
327 /// inject the exact same visible binding again.
328 pub fn with_durable_memory(
329 mut self,
330 binding: crate::durable_memory::DurableMemorySession,
331 ) -> Self {
332 self.durable_memory = Some(binding);
333 self
334 }
335
336 /// Use a file-based memory store at the given directory instead of the default.
337 ///
338 /// The store is created lazily when the session is built (requires async).
339 /// This stores the directory path; `FileMemoryStore::new()` is called during
340 /// session construction.
341 pub fn with_file_memory(mut self, dir: impl Into<PathBuf>) -> Self {
342 self.memory_store = None;
343 self.file_memory_dir = Some(dir.into());
344 self
345 }
346
347 /// Observe successful durable memory writes without replacing the memory
348 /// backend. Observers are best-effort derived projections: their failures
349 /// never undo a persisted memory.
350 pub fn with_memory_observer(
351 mut self,
352 observer: Arc<dyn crate::memory::MemoryObserver>,
353 ) -> Self {
354 self.memory_observers.push(observer);
355 self
356 }
357
358 /// Install typed scheduled memory jobs and their bounded close policy.
359 /// Built-in V1 pruning is included automatically when configured in
360 /// [`MemoryConfig`](crate::memory::MemoryConfig); verified semantic refresh
361 /// remains an explicit schedule in these options.
362 pub fn with_memory_maintenance(
363 mut self,
364 maintenance: crate::memory::MemoryMaintenanceOptions,
365 ) -> Self {
366 self.memory_maintenance = maintenance;
367 self
368 }
369
370 /// Set a session store for persistence
371 pub fn with_session_store(mut self, store: Arc<dyn crate::store::SessionStore>) -> Self {
372 self.session_store = Some(store);
373 self.file_session_store_dir = None;
374 self
375 }
376
377 /// Export exact portable checkpoints at completed tool-round boundaries.
378 ///
379 /// The sink receives an owned, canonical export only after all events from
380 /// that tool round have entered the matching Session snapshot. Sink errors
381 /// are warn-logged and never turn a healthy live Run into a failure.
382 pub fn with_session_checkpoint_export_sink(
383 mut self,
384 sink: Arc<dyn crate::session_checkpoint::SessionCheckpointExportSink>,
385 ) -> Self {
386 self.session_checkpoint_export_sink = Some(sink);
387 self
388 }
389
390 /// Use a file-based session store at the given directory.
391 ///
392 /// The path is a typed construction specification. No I/O occurs until
393 /// [`SessionBuilder::build`](super::SessionBuilder::build) is awaited.
394 pub fn with_file_session_store(mut self, dir: impl Into<PathBuf>) -> Self {
395 self.session_store = None;
396 self.file_session_store_dir = Some(dir.into());
397 self
398 }
399
400 /// Set an explicit session ID (auto-generated UUID if not set)
401 pub fn with_session_id(mut self, id: impl Into<String>) -> Self {
402 self.session_id = Some(id.into());
403 self
404 }
405
406 /// Tag the session with a host-defined tenant id. Opaque to the
407 /// framework — propagated to `SessionData`, hooks, and traces.
408 pub fn with_tenant_id(mut self, tenant: impl Into<String>) -> Self {
409 self.tenant_id = Some(tenant.into());
410 self
411 }
412
413 /// Tag the session with the id of the principal (user / service
414 /// account / etc.) that triggered it.
415 pub fn with_principal(mut self, principal: impl Into<String>) -> Self {
416 self.principal = Some(principal.into());
417 self
418 }
419
420 /// Tag the session with the id of the agent template / definition it
421 /// was instantiated from.
422 pub fn with_agent_template_id(mut self, template_id: impl Into<String>) -> Self {
423 self.agent_template_id = Some(template_id.into());
424 self
425 }
426
427 /// Attach a distributed-trace correlation id so this session's events
428 /// can be joined with upstream/downstream work.
429 pub fn with_correlation_id(mut self, corr: impl Into<String>) -> Self {
430 self.correlation_id = Some(corr.into());
431 self
432 }
433
434 /// Install a host-supplied [`BudgetGuard`](crate::budget::BudgetGuard).
435 ///
436 /// The guard is consulted before every LLM call (and after, for
437 /// usage accounting). When unset, no budget enforcement happens.
438 pub fn with_budget_guard(mut self, guard: Arc<dyn crate::budget::BudgetGuard>) -> Self {
439 self.budget_guard = Some(guard);
440 self
441 }
442
443 /// Install a host-provided [`HostEnv`](crate::host_env::HostEnv) for
444 /// deterministic ID generation and time. Replaces the framework
445 /// default of `uuid::Uuid::new_v4()` + wall clock — used by
446 /// host replay infrastructure to recreate a run bit-identical on
447 /// another node.
448 pub fn with_host_env(mut self, env: Arc<crate::host_env::HostEnv>) -> Self {
449 self.host_env = Some(env);
450 self
451 }
452
453 /// Install FIFO retention caps for the session's in-memory stores.
454 ///
455 /// Without these caps the in-memory run store, trace sink, and
456 /// subagent task tracker grow unboundedly across long-running
457 /// sessions. Hosts running thousands of long-lived sessions per
458 /// node should set sensible caps (e.g. retain the last 100 runs,
459 /// 5000 events per run, 10000 trace events, 1000 terminal subagent
460 /// tasks). When unset, the framework keeps every record — the
461 /// pre-existing behaviour.
462 pub fn with_retention_limits(
463 mut self,
464 limits: crate::retention::SessionRetentionLimits,
465 ) -> Self {
466 self.retention_limits = Some(limits);
467 self
468 }
469
470 /// Enable structured JSONL trajectory capture for this session.
471 ///
472 /// This is the preferred programmatic path for RL training and deployed
473 /// service data collection. Environment-only deployments can instead set
474 /// `A3S_CODE_TRAJECTORY_PATH`.
475 pub fn with_rl_trajectory(mut self, config: crate::rl_trajectory::RlTrajectoryConfig) -> Self {
476 self.rl_trajectory = Some(config);
477 self
478 }
479
480 /// Request token-level log probabilities from compatible LLM providers.
481 pub fn with_llm_logprobs(mut self, enabled: bool) -> Self {
482 self.llm_logprobs = Some(enabled);
483 self
484 }
485
486 /// Request up to `top_logprobs` alternative logprobs per generated token.
487 pub fn with_llm_top_logprobs(mut self, top_logprobs: usize) -> Self {
488 self.llm_logprobs = Some(true);
489 self.llm_top_logprobs = Some(top_logprobs);
490 self
491 }
492
493 /// Enable auto-save after each `send()` call
494 pub fn with_auto_save(mut self, enabled: bool) -> Self {
495 self.auto_save = enabled;
496 self
497 }
498
499 /// Set artifact retention limits for this session.
500 pub fn with_artifact_store_limits(mut self, limits: crate::tools::ArtifactStoreLimits) -> Self {
501 self.artifact_store_limits = Some(limits);
502 self
503 }
504
505 /// Install a session-scoped host adapter for authorized immutable Tool
506 /// content. Every raw output returned by a Tool writes through this port
507 /// before release; lossy projections expose its validated reference
508 /// instead of retaining a second local copy.
509 pub fn with_immutable_content_adapter(
510 mut self,
511 adapter: crate::tools::ImmutableContentAdapterSession,
512 ) -> Self {
513 self.immutable_content_adapter = Some(adapter);
514 self
515 }
516
517 /// Pin the deterministic projection policy for Tool results.
518 pub fn with_tool_result_transform_policy(
519 mut self,
520 policy: crate::tools::ToolResultTransformPolicyV1,
521 ) -> Self {
522 self.tool_result_transform_policy = Some(policy);
523 self
524 }
525
526 /// Select the typed model-facing Tool presentation profile.
527 pub fn with_tool_presentation_profile(
528 mut self,
529 profile: crate::tools::ToolPresentationProfileV1,
530 ) -> Self {
531 self.tool_presentation_profile = Some(profile);
532 self
533 }
534
535 /// Set the maximum number of consecutive malformed-tool-args errors before
536 /// the agent loop bails.
537 ///
538 /// Default: 2 (the LLM gets two chances to self-correct before the session
539 /// is aborted).
540 pub fn with_parse_retries(mut self, max: u32) -> Self {
541 self.max_parse_retries = Some(max);
542 self
543 }
544
545 /// Set a per-tool execution timeout.
546 ///
547 /// When set, each tool execution is wrapped in `tokio::time::timeout`.
548 /// A timeout produces an error message that is fed back to the LLM
549 /// (the session continues).
550 pub fn with_tool_timeout(mut self, timeout_ms: u64) -> Self {
551 self.tool_timeout_ms = Some(timeout_ms);
552 self
553 }
554
555 /// Set a per-model API HTTP timeout.
556 ///
557 /// This is separate from [`with_tool_timeout`](Self::with_tool_timeout):
558 /// tool calls may need long-running process limits while model API calls
559 /// should use provider/network-specific deadlines.
560 pub fn with_llm_api_timeout(mut self, timeout_ms: u64) -> Self {
561 self.llm_api_timeout_ms = Some(timeout_ms);
562 self
563 }
564
565 /// Set the circuit-breaker threshold.
566 ///
567 /// In non-streaming mode, the agent retries transient LLM API failures up
568 /// to this many times (with exponential backoff) before aborting.
569 /// Default: 3 attempts.
570 pub fn with_circuit_breaker(mut self, threshold: u32) -> Self {
571 self.circuit_breaker_threshold = Some(threshold);
572 self
573 }
574
575 /// Set the duplicate-tool-call threshold.
576 ///
577 /// When the same tool is called with identical arguments more than this
578 /// budget allows, the call is returned to the model as a failed tool result
579 /// instead of executing again. Default: 3.
580 pub fn with_duplicate_tool_call_threshold(mut self, threshold: u32) -> Self {
581 self.duplicate_tool_call_threshold = Some(threshold.max(1));
582 self
583 }
584
585 /// Enable all resilience defaults with sensible values:
586 ///
587 /// - `max_parse_retries = 2`
588 /// - `tool_timeout_ms = 120_000` (2 minutes)
589 /// - `circuit_breaker_threshold = 3`
590 pub fn with_resilience_defaults(self) -> Self {
591 self.with_parse_retries(2)
592 .with_tool_timeout(120_000)
593 .with_circuit_breaker(3)
594 }
595
596 /// Override the default native [`BashSandbox`] for this session.
597 ///
598 /// Local sessions automatically bind the A3S native sandbox. Use this
599 /// option only when the host owns another equivalent isolation boundary.
600 /// The host remains responsible for constructing and lifecycle-managing a
601 /// custom sandbox.
602 ///
603 /// [`BashSandbox`]: crate::sandbox::BashSandbox
604 pub fn with_sandbox_handle(mut self, handle: Arc<dyn crate::sandbox::BashSandbox>) -> Self {
605 self.sandbox_handle = Some(handle);
606 self
607 }
608
609 /// Provide a workspace backend for this session.
610 ///
611 /// Built-in tools keep their stable names and schemas, while their backing
612 /// implementation can target a DFS, browser workspace, remote runner, or
613 /// any other host-provided backend.
614 pub fn with_workspace_backend(
615 mut self,
616 services: Arc<crate::workspace::WorkspaceServices>,
617 ) -> Self {
618 self.workspace_services = Some(services);
619 self
620 }
621
622 /// Enable session-bound semantic workspace indexing.
623 ///
624 /// The session builder returns without waiting for corpus embeddings. The
625 /// caller can observe partial readiness through
626 /// [`AgentSession::workspace_retrieval_status`](super::AgentSession::workspace_retrieval_status).
627 pub fn with_workspace_retrieval(
628 mut self,
629 options: crate::workspace::WorkspaceRetrievalOptions,
630 ) -> Self {
631 self.workspace_retrieval = Some(options);
632 self
633 }
634
635 /// Explicitly disable session-bound semantic workspace indexing.
636 ///
637 /// This clears an earlier [`Self::with_workspace_retrieval`] choice without
638 /// constructing a replacement backend or calling the embedding provider.
639 /// It is useful when a host applies layered configuration and a later,
640 /// trusted layer deliberately opts the session out.
641 pub fn without_workspace_retrieval(mut self) -> Self {
642 self.workspace_retrieval = None;
643 self
644 }
645
646 /// Enable auto-compaction when context usage exceeds threshold.
647 ///
648 /// When enabled, the agent loop automatically prunes large tool outputs
649 /// and summarizes old messages when context usage exceeds the threshold.
650 pub fn with_auto_compact(mut self, enabled: bool) -> Self {
651 self.auto_compact = enabled;
652 self
653 }
654
655 /// Set the auto-compact threshold (0.0 - 1.0). Default: 0.80 (80%).
656 pub fn with_auto_compact_threshold(mut self, threshold: f32) -> Self {
657 self.auto_compact_threshold = Some(threshold.clamp(0.0, 1.0));
658 self
659 }
660
661 /// Set the active model's context window for compaction accounting.
662 pub fn with_max_context_tokens(mut self, tokens: usize) -> Self {
663 self.max_context_tokens = Some(tokens);
664 self
665 }
666
667 /// Enable or disable continuation injection (default: enabled).
668 ///
669 /// When enabled, the loop injects a continuation message when the LLM stops
670 /// calling tools before the task appears complete, nudging it to keep working.
671 pub fn with_continuation(mut self, enabled: bool) -> Self {
672 self.continuation_enabled = Some(enabled);
673 self
674 }
675
676 /// Set the maximum number of continuation injections per execution (default: 3).
677 pub fn with_max_continuation_turns(mut self, turns: u32) -> Self {
678 self.max_continuation_turns = Some(turns);
679 self
680 }
681
682 /// Inherit tools from an existing MCP manager.
683 ///
684 /// The session reads the manager as a capability source but never mutates
685 /// or disconnects it. Live [`AgentSession::add_mcp_server`](super::AgentSession::add_mcp_server)
686 /// calls use a separate session-owned manager. Delegated child agents
687 /// inherit both sources, with session-owned tools taking precedence.
688 pub fn with_mcp(mut self, manager: Arc<crate::mcp::manager::McpManager>) -> Self {
689 self.mcp_manager = Some(manager);
690 self
691 }
692
693 pub fn with_temperature(mut self, temperature: f32) -> Self {
694 self.temperature = Some(temperature);
695 self
696 }
697
698 pub fn with_thinking_budget(mut self, budget: usize) -> Self {
699 self.thinking_budget = Some(budget);
700 self
701 }
702
703 /// Override the maximum number of tool execution rounds for this session.
704 ///
705 /// Useful when binding a markdown-defined subagent to a session —
706 /// pass the agent definition's `max_steps` value here to enforce its step budget.
707 pub fn with_max_tool_rounds(mut self, rounds: usize) -> Self {
708 self.max_tool_rounds = Some(rounds);
709 self
710 }
711
712 /// Override the maximum number of sibling parallel branches for this session.
713 pub fn with_max_parallel_tasks(mut self, tasks: usize) -> Self {
714 self.max_parallel_tasks = Some(tasks.max(1));
715 self
716 }
717
718 /// Override automatic subagent delegation for this session.
719 pub fn with_auto_delegation(mut self, config: crate::config::AutoDelegationConfig) -> Self {
720 self.auto_delegation = Some(config);
721 self
722 }
723
724 /// Enable or disable automatic subagent delegation for this session.
725 pub fn with_auto_delegation_enabled(mut self, enabled: bool) -> Self {
726 let mut config = self.auto_delegation.take().unwrap_or_default();
727 config.enabled = enabled;
728 self.auto_delegation = Some(config);
729 self
730 }
731
732 /// Enable or disable manual child-agent tools for this session.
733 ///
734 /// When false, the model-visible `task` tool and the hidden `parallel_task`
735 /// compatibility alias are not registered. Worker agents remain registered
736 /// for introspection and hosts that manage them directly. This is for cost
737 /// control or debugging; it is not a security sandbox for the parent agent.
738 pub fn with_manual_delegation_enabled(mut self, enabled: bool) -> Self {
739 if let Some(config) = &mut self.auto_delegation {
740 config.allow_manual_delegation = enabled;
741 }
742 self.manual_delegation_enabled = Some(enabled);
743 self
744 }
745
746 /// Globally enable or disable automatic parallel child-agent fan-out.
747 ///
748 /// Manual `task` fan-out and legacy `parallel_task` calls remain available
749 /// when this is false.
750 pub fn with_auto_parallel_delegation(mut self, enabled: bool) -> Self {
751 if let Some(config) = &mut self.auto_delegation {
752 config.auto_parallel = enabled;
753 }
754 self.auto_parallel_delegation = Some(enabled);
755 self
756 }
757
758 /// Set slot-based system prompt customization for this session.
759 ///
760 /// Allows customizing role, guidelines, response style, and extra instructions
761 /// without overriding the core agentic capabilities.
762 pub fn with_prompt_slots(mut self, slots: SystemPromptSlots) -> Self {
763 self.prompt_slots = Some(slots);
764 self
765 }
766
767 /// Replace the built-in hook engine with an external hook executor.
768 ///
769 /// All lifecycle events are forwarded to the executor instead of the
770 /// in-process `HookEngine`.
771 pub fn with_hook_executor(mut self, executor: Arc<dyn crate::hooks::HookExecutor>) -> Self {
772 self.hook_executor = Some(executor);
773 self
774 }
775}