Skip to main content

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}