1212//! [`SubAgentOverrides`]; [`SubAgentSpawn::build`] validates each
1313//! override as a subset of the parent (using
1414//! [`SecurityPolicy::ensure_no_escalation_beyond`] for the policy and
15- //! a UUID-set containment check for the memory allowlist) and returns
16- //! `Err` with the originating violation chained on any escalation.
15+ //! an alias-set containment check for the memory allowlist) and
16+ //! returns `Err` with the originating violation chained on any
17+ //! escalation.
18+ //!
19+ //! The memory allowlist is carried as a set of agent **aliases** (the
20+ //! `[agents.<alias>]` config keys), not backend storage identifiers.
21+ //! Consumers that build an [`AgentScopedMemory`] must resolve aliases
22+ //! to backend identifiers via
23+ //! [`zeroclaw_memory::Memory::ensure_agent_uuid`] first — SQL-backed
24+ //! stores use UUIDs from the `agents` table; Markdown / Qdrant / None
25+ //! use the alias verbatim per the trait default. Holding aliases at
26+ //! this layer means [`SubAgentSpawn::for_agent`] does not need a
27+ //! backend handle to construct.
1728
1829use anyhow:: { Context , Result } ;
1930use std:: collections:: HashSet ;
@@ -36,41 +47,57 @@ pub struct SubAgentOverrides {
3647 /// [`SecurityPolicy::ensure_no_escalation_beyond`].
3748 pub policy : Option < SecurityPolicy > ,
3849 /// Override the SubAgent's memory allowlist (the set of sibling
39- /// agent UUIDs the SubAgent may recall from). Validated as a
40- /// subset of the parent's allowlist; any UUID present here that
41- /// is not on the parent's list is rejected.
42- pub allowed_agent_ids : Option < HashSet < String > > ,
50+ /// agent **aliases** the SubAgent may recall from, as written in
51+ /// `[agents.<alias>]` keys). Validated as a subset of the
52+ /// parent's allowlist; any alias here that is not on the parent's
53+ /// list is rejected.
54+ ///
55+ /// These are config-layer aliases, not backend storage
56+ /// identifiers. Consumers that build an [`AgentScopedMemory`]
57+ /// must resolve aliases to backend identifiers via
58+ /// [`zeroclaw_memory::Memory::ensure_agent_uuid`] before passing
59+ /// them to the wrapper (SQL backends use UUIDs; Markdown / Qdrant
60+ /// / None use the alias verbatim per the trait default). The
61+ /// in-tree consumer today is `zeroclaw_memory::create_memory_for_agent`,
62+ /// which performs the resolution.
63+ pub allowed_agent_aliases : Option < HashSet < String > > ,
4364}
4465
4566/// Constructed SubAgent context: bound parent identity, validated
4667/// child policy, and the resolved memory allowlist.
4768#[ derive( Debug , Clone ) ]
4869pub struct SubAgentContext {
49- /// The parent agent's identifier. SubAgents share the parent's
50- /// identity at the data layer (no separate row in the agents
51- /// table); the distinction between parent and sub-run is captured
52- /// at the tracing-span level (`agent.<alias>.subagent.<run_id>`).
53- pub agent_id : String ,
70+ /// The parent agent's alias (e.g. `"researcher"`). SubAgents share
71+ /// the parent's identity at the data layer (no separate row in the
72+ /// `agents` table); the distinction between parent and sub-run is
73+ /// captured at the tracing-span level
74+ /// (`agent.<alias>.subagent.<run_id>`).
75+ pub parent_alias : String ,
5476 /// The validated [`SecurityPolicy`] this SubAgent operates under.
5577 /// Identical to the parent's when `SubAgentOverrides::policy` is
5678 /// `None`; otherwise a narrowed copy that passed
5779 /// [`SecurityPolicy::ensure_no_escalation_beyond`].
5880 pub policy : Arc < SecurityPolicy > ,
59- /// Resolved memory allowlist. The bound `agent_id` is always
60- /// included so the SubAgent always sees the parent's own rows;
61- /// the rest is either the parent's allowlist verbatim or a
62- /// validated subset.
63- pub allowed_agent_ids : HashSet < String > ,
81+ /// Resolved memory allowlist as a set of agent **aliases**. The
82+ /// bound `parent_alias` is always included so the SubAgent always
83+ /// sees the parent's own rows; the rest is either the parent's
84+ /// allowlist verbatim or a validated subset.
85+ ///
86+ /// See [`SubAgentOverrides::allowed_agent_aliases`] for the
87+ /// alias-vs-backend-identifier distinction; consumers that build
88+ /// an [`AgentScopedMemory`] must resolve to backend identifiers
89+ /// before passing the set to the wrapper.
90+ pub allowed_agent_aliases : HashSet < String > ,
6491}
6592
6693/// Builder for a SubAgent spawn. The caller resolves a parent agent
6794/// from the loaded config; [`Self::build`] applies any narrowing
6895/// overrides and validates the result.
6996#[ derive( Debug ) ]
7097pub struct SubAgentSpawn {
71- pub parent_agent_id : String ,
98+ pub parent_alias : String ,
7299 pub parent_policy : Arc < SecurityPolicy > ,
73- pub parent_allowed_agent_ids : HashSet < String > ,
100+ pub parent_allowed_agent_aliases : HashSet < String > ,
74101}
75102
76103impl SubAgentSpawn {
@@ -91,18 +118,18 @@ impl SubAgentSpawn {
91118 format ! ( "could not resolve security policy for agent {agent_alias:?}" )
92119 } ) ?;
93120
94- let mut parent_allowed_agent_ids : HashSet < String > = agent
121+ let mut parent_allowed_agent_aliases : HashSet < String > = agent
95122 . workspace
96123 . read_memory_from
97124 . iter ( )
98125 . map ( |alias| alias. as_str ( ) . to_string ( ) )
99126 . collect ( ) ;
100- parent_allowed_agent_ids . insert ( agent_alias. to_string ( ) ) ;
127+ parent_allowed_agent_aliases . insert ( agent_alias. to_string ( ) ) ;
101128
102129 Ok ( Self {
103- parent_agent_id : agent_alias. to_string ( ) ,
130+ parent_alias : agent_alias. to_string ( ) ,
104131 parent_policy,
105- parent_allowed_agent_ids ,
132+ parent_allowed_agent_aliases ,
106133 } )
107134 }
108135
@@ -137,26 +164,26 @@ impl SubAgentSpawn {
137164 self . parent_policy . clone ( )
138165 } ;
139166
140- let allowed_agent_ids = if let Some ( child_allowed) = overrides. allowed_agent_ids {
141- for id in & child_allowed {
142- if !self . parent_allowed_agent_ids . contains ( id ) {
167+ let allowed_agent_aliases = if let Some ( child_allowed) = overrides. allowed_agent_aliases {
168+ for alias in & child_allowed {
169+ if !self . parent_allowed_agent_aliases . contains ( alias ) {
143170 anyhow:: bail!(
144- "subagent allowlist override contains agent_id {id :?} not present on \
171+ "subagent allowlist override contains alias {alias :?} not present on \
145172 parent's memory allowlist; SubAgent overrides may only narrow"
146173 ) ;
147174 }
148175 }
149176 let mut resolved = child_allowed;
150- resolved. insert ( self . parent_agent_id . clone ( ) ) ;
177+ resolved. insert ( self . parent_alias . clone ( ) ) ;
151178 resolved
152179 } else {
153- self . parent_allowed_agent_ids
180+ self . parent_allowed_agent_aliases
154181 } ;
155182
156183 Ok ( SubAgentContext {
157- agent_id : self . parent_agent_id ,
184+ parent_alias : self . parent_alias ,
158185 policy,
159- allowed_agent_ids ,
186+ allowed_agent_aliases ,
160187 } )
161188 }
162189}
@@ -189,9 +216,9 @@ mod tests {
189216 . expect ( "for_agent must succeed for a configured agent" )
190217 . build ( SubAgentOverrides :: default ( ) )
191218 . expect ( "inherits-verbatim build must succeed" ) ;
192- assert_eq ! ( ctx. agent_id , "alpha" ) ;
219+ assert_eq ! ( ctx. parent_alias , "alpha" ) ;
193220 assert ! (
194- ctx. allowed_agent_ids . contains( "alpha" ) ,
221+ ctx. allowed_agent_aliases . contains( "alpha" ) ,
195222 "an agent always sees its own rows"
196223 ) ;
197224 }
@@ -211,11 +238,11 @@ mod tests {
211238 let config = config_with_agent ( "alpha" ) ;
212239 let spawn = SubAgentSpawn :: for_agent ( & config, "alpha" ) . unwrap ( ) ;
213240 let parent_policy = spawn. parent_policy . clone ( ) ;
214- let parent_allowlist = spawn. parent_allowed_agent_ids . clone ( ) ;
241+ let parent_allowlist = spawn. parent_allowed_agent_aliases . clone ( ) ;
215242
216243 let ctx = spawn. build ( SubAgentOverrides :: default ( ) ) . unwrap ( ) ;
217244 assert ! ( Arc :: ptr_eq( & ctx. policy, & parent_policy) ) ;
218- assert_eq ! ( ctx. allowed_agent_ids , parent_allowlist) ;
245+ assert_eq ! ( ctx. allowed_agent_aliases , parent_allowlist) ;
219246 }
220247
221248 #[ test]
@@ -240,7 +267,7 @@ mod tests {
240267 }
241268
242269 #[ test]
243- fn build_rejects_allowlist_override_with_id_not_on_parent ( ) {
270+ fn build_rejects_allowlist_override_with_alias_not_on_parent ( ) {
244271 let config = config_with_agent ( "alpha" ) ;
245272 let spawn = SubAgentSpawn :: for_agent ( & config, "alpha" ) . unwrap ( ) ;
246273
@@ -249,13 +276,13 @@ mod tests {
249276
250277 let err = spawn
251278 . build ( SubAgentOverrides {
252- allowed_agent_ids : Some ( rogue) ,
279+ allowed_agent_aliases : Some ( rogue) ,
253280 ..SubAgentOverrides :: default ( )
254281 } )
255- . expect_err ( "allowlist override with foreign UUID must be rejected" ) ;
282+ . expect_err ( "allowlist override with foreign alias must be rejected" ) ;
256283 assert ! (
257284 err. to_string( ) . contains( "rogue-agent" ) ,
258- "expected the rogue UUID in the error chain, got: {err}"
285+ "expected the rogue alias in the error chain, got: {err}"
259286 ) ;
260287 }
261288
@@ -264,15 +291,15 @@ mod tests {
264291 let config = config_with_agent ( "alpha" ) ;
265292 let spawn = SubAgentSpawn :: for_agent ( & config, "alpha" ) . unwrap ( ) ;
266293
267- // Empty subset is still allowed; the bound agent_id is added back.
294+ // Empty subset is still allowed; the bound parent alias is added back.
268295 let ctx = spawn
269296 . build ( SubAgentOverrides {
270- allowed_agent_ids : Some ( HashSet :: new ( ) ) ,
297+ allowed_agent_aliases : Some ( HashSet :: new ( ) ) ,
271298 ..SubAgentOverrides :: default ( )
272299 } )
273300 . expect ( "narrowing to {} is a valid subset" ) ;
274- assert_eq ! ( ctx. allowed_agent_ids . len( ) , 1 ) ;
275- assert ! ( ctx. allowed_agent_ids . contains( "alpha" ) ) ;
301+ assert_eq ! ( ctx. allowed_agent_aliases . len( ) , 1 ) ;
302+ assert ! ( ctx. allowed_agent_aliases . contains( "alpha" ) ) ;
276303 }
277304
278305 #[ test]
0 commit comments