Part of #16946. Found while mapping #16948's delivery seams for each agent kind; out of #16948's own PR since Company OS has no internal turn boundary to reuse -- this is its own delivery mechanism.
Finding
llc/scheduler/heartbeat_scheduler.py's _poll_loop/_dispatch_due/_handle_due_agent treats one heartbeat run as one opaque, awaited unit -- no internal-step visibility, especially for external-CLI adapters (a subprocess black box from AutoBot's own code). The only real boundary is run-to-run (_run_adapter's start/finish), and even that is not serialized against the next scheduled fire (next-run timing is computed independently of current-run completion, by design).
So "next turn boundary" for a Company OS agent means: the start of its next heartbeat run, through that run's own context -- not mid-run.
Fix
Deliver a queued peer message into the recipient's next heartbeat run via its context (the same context_snapshot/recent_decisions enrichment _handle_due_agent already builds per run, heartbeat_scheduler.py), so the run's own agent sees it as part of what it's asked to do this time.
Authority bound: the message is judged against the org agent's own configured role (#16974) intersected with the sender's authority, per the chain-intersection ruling on #16946 -- a peer cannot use a Company OS agent's heartbeat to do something outside that agent's own role, regardless of what the sender could do itself.
Acceptance criteria
Related
blocked_by #16948
Part of #16946. Found while mapping #16948's delivery seams for each agent kind; out of #16948's own PR since Company OS has no internal turn boundary to reuse -- this is its own delivery mechanism.
Finding
llc/scheduler/heartbeat_scheduler.py's_poll_loop/_dispatch_due/_handle_due_agenttreats one heartbeat run as one opaque, awaited unit -- no internal-step visibility, especially for external-CLI adapters (a subprocess black box from AutoBot's own code). The only real boundary is run-to-run (_run_adapter's start/finish), and even that is not serialized against the next scheduled fire (next-run timing is computed independently of current-run completion, by design).So "next turn boundary" for a Company OS agent means: the start of its next heartbeat run, through that run's own context -- not mid-run.
Fix
Deliver a queued peer message into the recipient's next heartbeat run via its context (the same
context_snapshot/recent_decisionsenrichment_handle_due_agentalready builds per run,heartbeat_scheduler.py), so the run's own agent sees it as part of what it's asked to do this time.Authority bound: the message is judged against the org agent's own configured role (#16974) intersected with the sender's authority, per the chain-intersection ruling on #16946 -- a peer cannot use a Company OS agent's heartbeat to do something outside that agent's own role, regardless of what the sender could do itself.
Acceptance criteria
Related
blocked_by #16948