{{ message }}
Repository navigation
Conversation
When a delegated task reaches a terminal state through the child's own lifecycle, the parent is now told instead of having to poll: - idle parent → a notification turn starts right away - busy parent → the result queues and is delivered at the end of the parent's current turn (never interrupts) - a pull (get_delegation_status / cancel_delegation) marks the result consumed and stands the push down — exactly one winner The broker announces terminals through a new DelegationResultSink trait (no-op default, mirroring the other writer traits). The production sink owns per-parent queues, merges a fan-out into a single notification turn, retries with backoff while the parent is away (re-resolving the conversation's current connection, so a reopened session still gets its result), and expires undeliverable entries after 30 minutes — the result itself stays pull-able. Parent-initiated cancels and teardown drains never notify (those callers already know). Settings: delegation.result_delivery = push (default) | off, surfaced in the collaboration settings page (all 10 locales). Tests: delivery 16, broker 7, settings round-trip 1; cargo test --lib 5176 green; vitest 8873 green; tsc/eslint/clippy clean.
Round two of the result-push channel, responding to three findings: - The busy-path retry was a backoff-polling watcher. Every reference implementation is event-driven (codeg's own chat channel restores a deferred kickoff and re-fires on TurnComplete; ZCode, Claude Code and Codex all settle background work off in-process events). The watcher is gone: a terminal transition is served by an immediate attempt, a busy parent by the TurnComplete event, a parent that was away by SessionStarted (its conversation reconnects under a new connection id). The subscription is established synchronously on the first enqueue (the bus drops envelopes with no receivers), the TTL is pruned lazily on enqueue, and a per-parent flush guard keeps concurrent triggers from delivering one batch twice. - The notice was rendered as a user message in the parent's conversation. It is model-addressed system content: codeg's own delegation card deliberately does not inline child output, and no reference tool shows a task notification as user speech. Notices now carry a structural transcript marker (`src: delegation-result`) that the ACP-native parser preserves the turn split for but never renders, are never broadcast as `AcpEvent::UserMessage` (live views stay clean), and are dropped for agents whose history lives in their own store by the shared parser decorator matching the envelope (deliberately strict, biased toward under-matching — like the interrupt-marker precedent). - Capable adapters now take the notice mid-turn via `_session/steering` (`steer_notice`), with none of the feedback bookkeeping: no FeedbackItem, no FeedbackSubmitted, no pull-visible note. Also removed: the `delegation.result_delivery` setting. Proactive delivery is the mechanism, not a preference — the toggle, its i18n and its plumbing are gone. Settings audit: delegation (and every sibling domain) persists in `app_metadata`; nothing else to align. Tests: cargo 5186 green, clippy clean; vitest 8871 green; tsc/eslint clean.
…lushes per parent Two findings from a self-review of the push channel: - `enqueue` awaited the immediate delivery attempt (a DB write, a prompt enqueue, possibly a steering round-trip) on the broker's terminal path, contradicting the sink trait's own fire-and-forget contract. The attempt now runs in a spawned task; the terminal path returns after the queue insert. - The flush guard dropped a concurrent caller with `Settled`, which could silently swallow a `TurnComplete` that raced a delivery attempt (that entry would then wait for the next event). Replaced with a per-parent async gate: callers serialize, and the later one still runs its own pass over the updated queue. Gates live for the process lifetime — removing one while another caller holds its `Arc` would re-open the double-delivery window. Tests: two new (enqueue returns while a send is parked; concurrent flush triggers deliver exactly once), plus the suite made tolerant of the spawned attempt. cargo 5188 green, clippy clean.
CI lints --all-targets (not just --lib): an unused binding in the linked prompt test, a useless .into() in the notification slug test, and a format! with no arguments in the parser notice test.
… wording The push backend delivers delegated results at turn boundaries, but the tool descriptions and acks still taught pure pull, so parent models block-polled get_delegation_status in one never-ending turn. - tool_schema.json: get_delegation_status / delegate_to_agent / wait_ms now state that results are delivered proactively (end your turn and you will be woken); replace "just call again silently" with "prefer ending your turn over waiting in-turn" - broker.rs: running_ack / resume_ack say the result will be delivered automatically; resume_ack keeps its load-bearing "Delegation resumed" opening (Cursor title-sniff + isAffirmedResume contracts) - tests: pin both ack prefixes plus the "delivered to you automatically" keyword, and pin the schema's proactive-delivery wording (companion.rs)
Contributor
Author
main's HandshakeRun test helper predates the branch's Prompt.source field; the merged tree needs the field at this initializer too.
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What this adds
Delegated sub-agents are asynchronous:
delegate_to_agentreturns atask_idright away and the child keeps working in the background. Today the parent learns the outcome only by asking —get_delegation_status(a long-poll that parks until the child settles) orcancel_delegation. A parent that ends its turn before the child finishes — or an idle agent — never learns anything until someone prompts it again; the finished result just sits in the broker's cache. This PR pushes the terminal result to the parent.Delivery rules:
TurnCompleteevent — never a poll.SessionStarted), re-resolving the connection by conversation id — a reopened conversation has a new connection id._session/steeringchannel, gated by the samenative_steering_availablepolicy as user feedback; every other adapter keeps the queue path.get_delegation_status/cancel_delegationis marked consumed, and the queued push stands down — the parent is never told twice.Shape
delegation/delivery.rs(new):DelegationResultSinktrait mirroring the existing writer-trait pattern (DelegationMetaWriter/DelegationEventEmitter/ChildStatusLookup) so the broker stays transport-free;DeliverySink(per-parent queue, merge, lazy TTL, per-parent flush gate);ManagerDeliveryTransport(resolve → folder →send_prompt_linked/ steer). The broker announces terminals fromcomplete_call; the attempt runs in a spawned task — the terminal path must not ride on delivery I/O.InternalEventBus— the same event-driven retry the chat channel's deferred kickoff uses. No timers, no polling.TranscriptEntrygains an optionalsrcmarker (absent on everything else; old readers are unaffected). Notice prompts recordsrc: "delegation-result".AcpEvent::UserMessage(live views stay clean), and history readers keep the turn split without rendering a user turn —acp_nativedrops it structurally, and the sharedRouteSanitizeddecorator does the same for agents whose history lives in their own store, matching the envelope as a strict signature with the same under-matching bias asclaude::is_interrupt_marker. Rationale: the delegation card is deliberately status + navigation only (it does not inline child output), and no reference implementation shows a background-task notification as user speech (ZCode attaches it to the tool call, Claude Code to the Agent card / Fleet view, Codex has no injection).Compatibility
complete_callgains one sink call. The existing pull path is untouched (verified: a task answered by a pull produces no push).Verification
cargo test --lib: full suite green on this branch (5234 passed on 0.35.1) (new: 21 delivery unit tests — immediate/idle delivery, busy queue + merge,TurnComplete/SessionStartedhooks end to end on a real bus, pull-consumption, duplicate-terminal idempotency, TTL, steer fallback, non-blocking enqueue, concurrent-flush safety; plus parser 4, manager 3, transcript 2).cargo clippy --lib --all-features -- -D warnings: clean.get_delegation_status);TurnCompleteevent the same second;Notes
broker.rsand adjacent delegation paths; fix(delegation): replay the stored task and reopen a custom session with no agent turn #932 (delegation resume replay) touches similar files, so whichever lands second may need a trivial rebase.registry::steering_prompt_required_min_versiontoday lists Claude Code only) — those parents ride the queue, which is the behavior exercised live above.