Problem
crates/stella-core/src/subagent.rs:451-472 (AgentAttribution) saves the previous ambient agent id and restores it on drop; HookBus::set_agent (bus.rs:591-594) mutates one process-shared Mutex<Context>. Correct only under strict LIFO nesting — concurrent siblings (live once PR #1836 lands) violate it: A enters (prev None), B enters (prev Some(a)), A drops → ambient None while B runs, B drops → ambient permanently Some(a); the parent's remaining bus events are attributed to a finished child.
Not live today: both dispatchers build SubAgentHost::new with no bus (stella-cli/src/subagent.rs:378, stella-serve/src/subagents.rs:265), so enter is a no-op. It arms the instant anyone calls SubAgentHost::with_bus — and stella-serve/src/session.rs:764 already attaches a bus to the engine, so this is the obvious next step for someone.
Fix direction
Make attribution a stack or (better) thread-local/task-local carried by the child's own execution context, not a global slot. If that is deferred, put a debug_assert/comment on with_bus refusing concurrent-sibling use so the trap is at least fenced.
Verify
Witness: two overlapping AgentAttribution scopes on one bus must leave the ambient id equal to the pre-entry value after both drop, and events emitted between drops must carry the still-running child's id.
Constraints
stella-core; bus.rs is a god file — plan new code into a sibling module.
Problem
crates/stella-core/src/subagent.rs:451-472(AgentAttribution) saves the previous ambient agent id and restores it on drop;HookBus::set_agent(bus.rs:591-594) mutates one process-sharedMutex<Context>. Correct only under strict LIFO nesting — concurrent siblings (live once PR #1836 lands) violate it: A enters (prev None), B enters (prev Some(a)), A drops → ambient None while B runs, B drops → ambient permanently Some(a); the parent's remaining bus events are attributed to a finished child.Not live today: both dispatchers build
SubAgentHost::newwith no bus (stella-cli/src/subagent.rs:378, stella-serve/src/subagents.rs:265), soenteris a no-op. It arms the instant anyone callsSubAgentHost::with_bus— and stella-serve/src/session.rs:764 already attaches a bus to the engine, so this is the obvious next step for someone.Fix direction
Make attribution a stack or (better) thread-local/task-local carried by the child's own execution context, not a global slot. If that is deferred, put a debug_assert/comment on
with_busrefusing concurrent-sibling use so the trap is at least fenced.Verify
Witness: two overlapping AgentAttribution scopes on one bus must leave the ambient id equal to the pre-entry value after both drop, and events emitted between drops must carry the still-running child's id.
Constraints
stella-core; bus.rs is a god file — plan new code into a sibling module.