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