Files
krow_backend/go-api/internal/runtime/trajectory.go
Suriyakumarvijayanayagam 6849363a37
Some checks failed
CI / test (push) Has been cancelled
CI / fixture (push) Has been cancelled
Implement delegation, so an agent's subagents are more than decoration
krow-workforce-agent has declared five subagents since it was written and
answered every question by itself. Everything for §6 existed except the
delegation: the parser read `subagents:`, runtime.Agent carried them, the
loader populated them, agent_runs had a parent_run_id column with a
self-reference and a no-self-parent constraint, and budget.go's comments
already described sharing a budget with subagents. Nothing called any of it.

A subagent is offered to the parent's model as a tool, because §6 says that is
what delegation is from the parent's side. Three rules are enforced rather than
assumed, each with a test that fails if it stops holding:

  I1  The subagent runs as the ORIGINAL caller. It cannot read anything the
      person could not read directly.
  §6  It SHARES the parent's budget. The test sets MaxSteps to 1, spends it in
      the parent, and asserts the child terminates BudgetExceeded — an
      assertion that only passes when the budget is shared, and that a fresh
      budget would quietly turn green.
  §3  Depth is capped at 2. At the cap no subagent is loaded or offered, so a
      cycle reaching run time is bounded rather than unbounded.

I4 survives too: a write a SUBAGENT wants approved still stops the whole run
and asks a person, rather than being performed because it happened one level
down.

Two bugs found by running it rather than by reading it:

  - delegate() read the error before the result. finish returns a non-nil
    error for every termination that is not Completed, INCLUDING
    ConfirmationPending — which is not a failure but a run that stopped to ask
    a question. Reading the error first discarded the result and with it the
    confirmation, so a subagent's write silently never happened and nobody was
    asked.

  - Delegated trajectories were never persisted at all. parent_run_id is a
    foreign key and a subagent finishes BEFORE the run that delegated to it,
    so every child insert named a parent row that did not exist yet. The
    database refused it; finish deliberately does not fail a run over a sink
    error; and the entry recording that the trajectory could not be saved was
    itself in the trajectory that was not saved. Children are now buffered and
    written by finish after the parent's own row, each arriving with its
    descendants already ordered behind it, so one pass writes a whole tree
    parent-first. The regression test asserts on save ORDER, because a
    MemorySink has no foreign key and will pass either way.

Verified end to end against a live model: an agent with no tools of its own and
one subagent produced

    delegation-probe   run=run_16622d7de6  parent=(root)
    talent-pool-agent  run=run_64160684b1  parent=run_16622d7de6

with the subagent's answer reaching the parent's model. Full suite green, only
TestLive* skipped.

Not addressed: §3's publish-time cycle detection, which needs the whole agent
set in hand. The depth cap is what holds without it, and is the half that
matters at run time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJvibeSc1JYXjatankqM1g
2026-08-29 11:59:20 +05:30

10 KiB