Skip to content

Task Input and Redirection

Additional evidence and changes of scope do not require a new child task. Send input to a running task, or interrupt its current attempt and continue the same conversation. The task handle, result schema, delegation budget, and deadline remain unchanged.

Send From Kedi Code

> profile: reviewer:
    > adapter: pydantic
    > model: openai:gpt-5.6-luna
    > instructions: Review the supplied evidence. State uncertainties explicitly.

> profile: coordinator:
    > adapter: pydantic
    > subagent: reviewer

> use: coordinator
[change] = Add an export endpoint. Integration tests have not run.
[migration_notes] = Existing access tokens retain their previous permissions.

> task [review_job]: reviewer:
    >> The security concerns in <change> are [concerns: list[str]].

> send: review_job:
    >> Include the migration evidence from <migration_notes>.

> await [review]: review_job
> show: <`review.output.concerns`>

> send does not cancel an active request or tool. The child receives the input at the next model-input boundary. If it would otherwise finish, the accepted input must be processed before the coordinator publishes the final result. Do not assume that a very short task is still running: sending to a completed task is rejected. Use continuation for finished work.

An input body accepts one >> template with continuation lines. Substitutions are evaluated once at send time. Captures, nested directives, and additional templates are rejected during parsing.

Redirect the Current Attempt

> interrupt: review_job:
    >> The user changed the scope. Review only authentication and authorization.

Interruption and the updated input are one operation. The old attempt cannot publish a result for the new revision. Kedi waits for it to stop, cancels its owned descendants, and resumes using the retained conversation. Sibling tasks continue. Kedi retains completed tool results instead of automatically repeating those calls. The model can still choose to call a tool again; interruption cannot undo completed host effects. Use idempotency keys for operations that must not be applied twice.

Change One Task, Keep the Other Running

The review and release note are separate tasks. Redirecting the review does not restart the release note or consume another delegation slot:

> model: openai:gpt-5.6-luna

> profile: reviewer:
    > adapter: pydantic
    > instructions: Review the supplied change and identify concrete security concerns.

> profile: writer:
    > adapter: pydantic
    > instructions: Write a release note using only the supplied facts.

> profile: coordinator:
    > adapter: pydantic
    > subagent: reviewer
    > subagent: writer
    > max_agents: 2

> use: coordinator
[change] = Add an export endpoint. Existing access tokens retain their permissions.

> task [review_job]: reviewer:
    >> The concerns in <change> are [concerns: list[str]].

> task [note_job]: writer:
    >> The release note for <change> is [note: str].

> interrupt: review_job:
    >> The user changed the scope. Review only access-token authorization.

> await [review]: review_job
> await [release]: note_job
> show: <`review.output.concerns`>
> show: <`release.output.note`>

Another interrupt during cleanup is rejected as busy. Ordinary messages can still queue for the replacement attempt. Cancellation, timeout, and owner shutdown terminate the logical task instead of restarting it. A synchronous tool running in a thread cannot be forcibly killed: if cleanup is not confirmed within the bounded cleanup waits (five seconds per phase), the task fails instead of starting overlapping work.

Python Handles and Receipts

async with runtime.subagents(parent="coordinator") as agents:
    job = await agents.start("reviewer", task="Review the export endpoint.")
    receipt = await job.send("Include the migration evidence.")
    if receipt.status == "rejected":
        print(receipt.reason)
    result = await job.wait()
    print(result.task_summary)

job.task_id aliases job.run_id. Repeated waits return the same final result, including after redirection. Use interrupt=True with send to redirect work.

Receipt field Meaning
message_id Runtime-assigned identifier; None on rejection
target_id Logical task receiving the input
revision Current task revision; increases on accepted interruption
status queued, interrupt_requested, or rejected
reason Stable rejection reason, or None

Rejection reasons are task_finished, not_authorized, unsupported, busy, queue_full, and input_too_large. Admission does not mean the model has read, understood, or acted on the input. The queue holds at most 32 pending messages, 128 KiB total, and 32 KiB per message, measured as UTF-8. Accepted input is not silently evicted or truncated.

Input events record admission, confirmed delivery, or undelivered on task termination. An admitted message is not guaranteed delivery after cancellation, timeout, or failure. Pending input is released when the task closes. Initial task input is confirmed at the native request boundary, not merely when Kedi assembles a prompt. A request rejected before that boundary, including one rejected by native usage limits or Kedi's run budget, does not produce a delivery event. Requests started before interruption remain charged; caller-supplied cumulative usage is retained across continuations. Delivery does not imply a successful response or model comprehension. History processors must preserve the current request tail, including its task input.

If a Codex turn completes just before its control request is handled, Kedi keeps the undelivered input for the next turn in the same conversation. It does not mark a rejected steer as delivered. Other native queue or transport failures remain visible; Kedi does not busy-retry an invalid enqueue. Interruption pins the obsolete attempt's revision through nested runtime scopes, so that attempt cannot start new tools or child tasks after its scope has been superseded. If native stop confirmation fails or is cancelled, Kedi attempts bounded local cleanup and reports the failure instead of starting replacement work. Already completed host effects are not rolled back.

Model-Driven Communication

Parents receive send_subagent_input(task_id, message, interrupt=False) in their toolset. Children receive send_parent_message(message). A child cannot select an arbitrary recipient; it can address only its direct parent. A parent can address only its own direct child tasks, not siblings or another invocation's tasks. Input cannot alter tools, permissions, models, or output schemas.

A child message does not pause that child or guarantee a reply. It enters the parent's next model request. A model-facing wait_subagent can return a running handle with input_available=true when such input arrives. The parent must wait again for the terminal child result. Native > await and Python job.wait() are not changed into nonterminal waits.

A deterministic Python owner receives updates explicitly:

messages = await agents.receive_messages()
for message in messages:
    print(message.sender_id, message.message)

This API does not create a parent model request. Messages and task handles are invocation-owned and cannot be reused after the scope closes.

Adapter Support

Pydantic AI uses native enqueue and cancellation; LangChain uses request-boundary middleware, async cancellation, and invocation-local graph checkpoints. Codex uses turn/steer, or interrupt followed by terminal acknowledgment and same-thread continuation. Claude uses tool/stop hooks and a controllable SDK client; a pending message blocks final stopping until the next input boundary.

Pydantic callers can still supply their own CancellationToken. Cancelling that token cancels its registered runs; redirecting one Kedi task uses run-local cancellation and does not cancel siblings that share the caller's token.

These adapters expose the same public tools, receipts, ownership checks, and terminal waits. A2A, ACP, and DSPy reject live input as unsupported. Pydantic AI versions without native enqueue support also reject it. No adapter promises that interrupting work reverses already completed host effects.