@@ -348,6 +348,8 @@ pub struct DefaultsConfig {
348348 pub history_backfill_count : usize ,
349349 pub cron : Vec < CronDef > ,
350350 pub opencode : OpenCodeConfig ,
351+ /// ACP agent definitions shared across all agents.
352+ pub acp : HashMap < String , AcpAgentConfig > ,
351353 /// Worker log mode: "errors_only", "all_separate", or "all_combined".
352354 pub worker_log_mode : crate :: settings:: WorkerLogMode ,
353355}
@@ -376,6 +378,7 @@ impl std::fmt::Debug for DefaultsConfig {
376378 . field ( "history_backfill_count" , & self . history_backfill_count )
377379 . field ( "cron" , & self . cron )
378380 . field ( "opencode" , & self . opencode )
381+ . field ( "acp" , & self . acp )
379382 . field ( "worker_log_mode" , & self . worker_log_mode )
380383 . finish ( )
381384 }
@@ -567,6 +570,40 @@ impl Default for OpenCodeConfig {
567570 }
568571}
569572
573+ /// ACP (Agent Client Protocol) agent configuration.
574+ ///
575+ /// Configured under `[defaults.acp.<id>]` in config.toml. Each entry
576+ /// represents a separate ACP-compatible coding agent that Spacebot can spawn
577+ /// and communicate with over stdio.
578+ #[ derive( Debug , Clone ) ]
579+ pub struct AcpAgentConfig {
580+ /// Unique identifier for this ACP agent (the TOML table key).
581+ pub id : String ,
582+ /// Whether this ACP agent is available for use.
583+ pub enabled : bool ,
584+ /// Path to the agent binary (supports "env:VAR_NAME" references).
585+ pub command : String ,
586+ /// Arguments passed to the agent binary.
587+ pub args : Vec < String > ,
588+ /// Environment variables set when spawning the agent process.
589+ pub env : HashMap < String , String > ,
590+ /// Session timeout in seconds. `None` uses a default of 300s.
591+ pub timeout : u64 ,
592+ }
593+
594+ impl Default for AcpAgentConfig {
595+ fn default ( ) -> Self {
596+ Self {
597+ id : String :: new ( ) ,
598+ enabled : true ,
599+ command : String :: new ( ) ,
600+ args : Vec :: new ( ) ,
601+ env : HashMap :: new ( ) ,
602+ timeout : 300 ,
603+ }
604+ }
605+ }
606+
570607/// Cortex configuration.
571608#[ derive( Debug , Clone , Copy ) ]
572609pub struct CortexConfig {
@@ -766,6 +803,8 @@ pub struct AgentConfig {
766803 pub cron_timezone : Option < String > ,
767804 /// Sandbox configuration for process containment.
768805 pub sandbox : Option < crate :: sandbox:: SandboxConfig > ,
806+ /// Per-agent ACP overrides. None inherits from defaults.
807+ pub acp : Option < HashMap < String , AcpAgentConfig > > ,
769808 /// Cron job definitions for this agent.
770809 pub cron : Vec < CronDef > ,
771810}
@@ -816,6 +855,7 @@ pub struct ResolvedAgentConfig {
816855 pub sandbox : crate :: sandbox:: SandboxConfig ,
817856 /// Number of messages to fetch from the platform when a new channel is created.
818857 pub history_backfill_count : usize ,
858+ pub acp : HashMap < String , AcpAgentConfig > ,
819859 pub cron : Vec < CronDef > ,
820860}
821861
@@ -841,6 +881,7 @@ impl Default for DefaultsConfig {
841881 history_backfill_count : 50 ,
842882 cron : Vec :: new ( ) ,
843883 opencode : OpenCodeConfig :: default ( ) ,
884+ acp : HashMap :: new ( ) ,
844885 worker_log_mode : crate :: settings:: WorkerLogMode :: default ( ) ,
845886 }
846887 }
@@ -898,6 +939,7 @@ impl AgentConfig {
898939 ) ,
899940 sandbox : self . sandbox . clone ( ) . unwrap_or_default ( ) ,
900941 history_backfill_count : defaults. history_backfill_count ,
942+ acp : resolve_acp_configs ( & defaults. acp , self . acp . as_ref ( ) ) ,
901943 cron : self . cron . clone ( ) ,
902944 }
903945 }
@@ -1720,6 +1762,8 @@ struct TomlDefaultsConfig {
17201762 brave_search_key : Option < String > ,
17211763 cron_timezone : Option < String > ,
17221764 opencode : Option < TomlOpenCodeConfig > ,
1765+ #[ serde( default ) ]
1766+ acp : HashMap < String , TomlAcpAgentConfig > ,
17231767 worker_log_mode : Option < String > ,
17241768}
17251769
@@ -1820,6 +1864,18 @@ struct TomlOpenCodePermissions {
18201864 webfetch : Option < String > ,
18211865}
18221866
1867+ #[ derive( Deserialize , Clone , Default ) ]
1868+ struct TomlAcpAgentConfig {
1869+ #[ serde( default = "default_enabled" ) ]
1870+ enabled : bool ,
1871+ command : Option < String > ,
1872+ #[ serde( default ) ]
1873+ args : Vec < String > ,
1874+ #[ serde( default ) ]
1875+ env : HashMap < String , String > ,
1876+ timeout : Option < u64 > ,
1877+ }
1878+
18231879#[ derive( Deserialize , Clone ) ]
18241880struct TomlMcpServerConfig {
18251881 name : String ,
@@ -1866,6 +1922,8 @@ struct TomlAgentConfig {
18661922 cron_timezone : Option < String > ,
18671923 sandbox : Option < crate :: sandbox:: SandboxConfig > ,
18681924 #[ serde( default ) ]
1925+ acp : Option < HashMap < String , TomlAcpAgentConfig > > ,
1926+ #[ serde( default ) ]
18691927 cron : Vec < TomlCronDef > ,
18701928}
18711929
@@ -2173,6 +2231,20 @@ fn resolve_mcp_configs(
21732231 merged
21742232}
21752233
2234+ /// Merge default ACP configs with optional per-agent overrides.
2235+ fn resolve_acp_configs (
2236+ default_configs : & HashMap < String , AcpAgentConfig > ,
2237+ agent_configs : Option < & HashMap < String , AcpAgentConfig > > ,
2238+ ) -> HashMap < String , AcpAgentConfig > {
2239+ let mut merged = default_configs. clone ( ) ;
2240+ if let Some ( overrides) = agent_configs {
2241+ for ( id, cfg) in overrides {
2242+ merged. insert ( id. clone ( ) , cfg. clone ( ) ) ;
2243+ }
2244+ }
2245+ merged
2246+ }
2247+
21762248impl Config {
21772249 /// Resolve the instance directory from env or default (~/.spacebot).
21782250 pub fn default_instance_dir ( ) -> PathBuf {
@@ -2546,6 +2618,7 @@ impl Config {
25462618 brave_search_key: None ,
25472619 cron_timezone: None ,
25482620 sandbox: None ,
2621+ acp: None ,
25492622 cron: Vec :: new( ) ,
25502623 } ] ;
25512624
@@ -3157,6 +3230,41 @@ impl Config {
31573230 }
31583231 } )
31593232 . unwrap_or_else ( || base_defaults. opencode . clone ( ) ) ,
3233+ acp : {
3234+ let mut merged = base_defaults. acp . clone ( ) ;
3235+ for ( id, toml_acp) in & toml. defaults . acp {
3236+ let base_entry = merged. get ( id) ;
3237+ let resolved_command = toml_acp
3238+ . command
3239+ . as_deref ( )
3240+ . and_then ( resolve_env_value)
3241+ . or_else ( || toml_acp. command . clone ( ) )
3242+ . or_else ( || base_entry. map ( |b| b. command . clone ( ) ) )
3243+ . unwrap_or_default ( ) ;
3244+ merged. insert (
3245+ id. clone ( ) ,
3246+ AcpAgentConfig {
3247+ id : id. clone ( ) ,
3248+ enabled : toml_acp. enabled ,
3249+ command : resolved_command,
3250+ args : if toml_acp. args . is_empty ( ) {
3251+ base_entry. map ( |b| b. args . clone ( ) ) . unwrap_or_default ( )
3252+ } else {
3253+ toml_acp. args . clone ( )
3254+ } ,
3255+ env : if toml_acp. env . is_empty ( ) {
3256+ base_entry. map ( |b| b. env . clone ( ) ) . unwrap_or_default ( )
3257+ } else {
3258+ toml_acp. env . clone ( )
3259+ } ,
3260+ timeout : toml_acp
3261+ . timeout
3262+ . unwrap_or_else ( || base_entry. map ( |b| b. timeout ) . unwrap_or ( 300 ) ) ,
3263+ } ,
3264+ ) ;
3265+ }
3266+ merged
3267+ } ,
31603268 worker_log_mode : toml
31613269 . defaults
31623270 . worker_log_mode
@@ -3308,6 +3416,30 @@ impl Config {
33083416 brave_search_key : a. brave_search_key . as_deref ( ) . and_then ( resolve_env_value) ,
33093417 cron_timezone : a. cron_timezone . as_deref ( ) . and_then ( resolve_env_value) ,
33103418 sandbox : a. sandbox ,
3419+ acp : a. acp . map ( |acp_map| {
3420+ acp_map
3421+ . into_iter ( )
3422+ . map ( |( id, toml_acp) | {
3423+ let resolved_command = toml_acp
3424+ . command
3425+ . as_deref ( )
3426+ . and_then ( resolve_env_value)
3427+ . or_else ( || toml_acp. command . clone ( ) )
3428+ . unwrap_or_default ( ) ;
3429+ (
3430+ id. clone ( ) ,
3431+ AcpAgentConfig {
3432+ id,
3433+ enabled : toml_acp. enabled ,
3434+ command : resolved_command,
3435+ args : toml_acp. args ,
3436+ env : toml_acp. env ,
3437+ timeout : toml_acp. timeout . unwrap_or ( 300 ) ,
3438+ } ,
3439+ )
3440+ } )
3441+ . collect ( )
3442+ } ) ,
33113443 cron,
33123444 } )
33133445 } )
@@ -3337,6 +3469,7 @@ impl Config {
33373469 brave_search_key : None ,
33383470 cron_timezone : None ,
33393471 sandbox : None ,
3472+ acp : None ,
33403473 cron : Vec :: new ( ) ,
33413474 } ) ;
33423475 }
@@ -3625,6 +3758,8 @@ pub struct RuntimeConfig {
36253758 pub opencode : ArcSwap < OpenCodeConfig > ,
36263759 /// Shared pool of OpenCode server processes. Lazily initialized on first use.
36273760 pub opencode_server_pool : Arc < crate :: opencode:: OpenCodeServerPool > ,
3761+ /// ACP agent definitions for this runtime.
3762+ pub acp : ArcSwap < HashMap < String , AcpAgentConfig > > ,
36283763 /// Cron store, set after agent initialization.
36293764 pub cron_store : ArcSwap < Option < Arc < crate :: cron:: CronStore > > > ,
36303765 /// Cron scheduler, set after agent initialization.
@@ -3680,6 +3815,7 @@ impl RuntimeConfig {
36803815 skills : ArcSwap :: from_pointee ( skills) ,
36813816 opencode : ArcSwap :: from_pointee ( defaults. opencode . clone ( ) ) ,
36823817 opencode_server_pool : Arc :: new ( server_pool) ,
3818+ acp : ArcSwap :: from_pointee ( agent_config. acp . clone ( ) ) ,
36833819 cron_store : ArcSwap :: from_pointee ( None ) ,
36843820 cron_scheduler : ArcSwap :: from_pointee ( None ) ,
36853821 settings : ArcSwap :: from_pointee ( None ) ,
0 commit comments