mirror of
https://github.com/0xWheatyz/handler.git
synced 2026-08-30 11:36:24 +00:00
Agents can hand work to agents: dispatch_agent
A schedule is a time trigger, and only the first step of a pipeline is really waiting on time — every later step waits on the previous step's result. Modeling "watch a source -> write a spec -> implement it" as three schedules made each fire blind: on a quiet day the coding agent still spawned, paid a full model run to find there was nothing to do, and left an empty run in Activity. So an agent can start the next step itself. `dispatch_agent` (a tool on the bundled MCP server, and on the pi bridge through the same --call seam) enqueues an ordinary spawn command in the agent's own project, tagged requested_by=agent:<id> — so a handoff is visible in Activity with no new surface to build. - Project-scoped by construction: project_id is read from the spawn environment and never from the tool arguments. - Bounded rather than gated: MAX_DISPATCH_PER_RUN counts the command rows the agent already wrote; MAX_DISPATCH_DEPTH rides in the spawn payload and is recovered by spawn._dispatch_depth, so a chain keeps its place across a resume and a cycle terminates instead of fanning out. - New scout and planner roles, with built-in skills (handler-scout, handler-planner, handler-dispatch) carrying the judgment code can't: dedupe against a memory-note watermark, treat "nothing new" as a complete run, and write a task the receiving cold-start agent can act on. - A scout ending on a clean tree skips the test gate and records the new tests_status='skipped' (migration 0017, additive CHECK widening). The gate promises `done` means tests passed for the work that shipped; nothing shipped. Rejected a `condition` field on schedules: "is this paper new and does it matter here?" is a semantic judgment, so it belongs to a model, not a scheduler column. The scout is the condition; dispatch is how it reports true — one mechanism that covers future pipelines too. 426 tests (14 new for dispatch, 3 for the gate exemption). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HcbDevyMcJWE6qPA56C7mZ
This commit is contained in:
@@ -244,6 +244,44 @@ The static shell is served **unauthenticated** (it holds no data); the browser p
|
||||
|
||||
**Out of scope (additive follow-ups):** an aggregate `GET /projects/{project}/overview` (agents + latest checkmark in one call) to show every agent's checkmark at once; spawning agents / registering projects from the UI (still CLI-driven); shared-context **writes** from the UI (would need the shared-write token — MVP is read-only).
|
||||
|
||||
### Dynamic workflows — agent-initiated dispatch
|
||||
|
||||
Design decision (operator, 2026-08-19): a recurring **pipeline** is not N schedules. A
|
||||
schedule is a time trigger, and only the first step of a pipeline is genuinely waiting
|
||||
on time — every later step is waiting on the previous step's *result*. Scheduling them
|
||||
independently means each fires blind, and the expensive steps pay a full model run on
|
||||
quiet days just to discover there is nothing to do.
|
||||
|
||||
Rejected: a `condition` field on `schedules`. The conditions that matter here ("is this
|
||||
paper new, and does it bear on this project?") are semantic judgments, so they belong
|
||||
to a model, not to a scheduler column. The scout **is** the condition; `dispatch_agent`
|
||||
is how it reports true. One mechanism then covers every future pipeline rather than
|
||||
this one.
|
||||
|
||||
- [x] `dispatch_agent` on the bundled MCP server (+ pi bridge via the same `--call`
|
||||
seam): enqueues an ordinary `spawn` command tagged `requested_by = agent:<id>`,
|
||||
so a handoff is visible in Activity with no new surface. `project_id` is read
|
||||
from the spawn environment and never from the arguments — project isolation holds
|
||||
by construction rather than by validation.
|
||||
- [x] Bounded rather than gated (operator decision: dispatches run immediately, no
|
||||
approval queue): `max_dispatch_per_run` counted off the command rows the agent
|
||||
already wrote, and `max_dispatch_depth` carried in the spawn payload and
|
||||
recovered by `spawn._dispatch_depth`, so a chain keeps its place across a resume
|
||||
and a cycle terminates. Any agent may dispatch — one general primitive, so a
|
||||
later chain (a junior splitting off a follow-up) needs no further change.
|
||||
- [x] `scout` + `planner` roles with built-in skills carrying the judgment code can't:
|
||||
dedupe against a memory-note watermark (better than a repo file — no checkout, no
|
||||
commit, and `SessionStart` recall delivers it for free), treat "nothing new" as a
|
||||
complete run, and write a task the receiving cold-start agent can act on.
|
||||
- [x] A `scout` on a clean tree skips the test gate (`tests_status = 'skipped'`,
|
||||
migration `0017_gate_skipped`). The gate promises `done` means tests passed *for
|
||||
the work that shipped*; nothing shipped. The clean-tree condition is what keeps
|
||||
that honest.
|
||||
|
||||
**Definition of done:** one cheap schedule fires a scout; a quiet run costs one
|
||||
small-model call and enqueues nothing; a run that finds something dispatches a planner,
|
||||
which commits a spec and dispatches a junior into the existing forge workflow. 426 tests.
|
||||
|
||||
### Phase 4 — Observability (moved back, now optional)
|
||||
- [ ] Prometheus metrics endpoint on the API (agent counts, pending questions, checkpoint rate)
|
||||
- [ ] Grafana/Loki wiring documented as an optional add-on for self-hosters who already run that stack — not a dependency for anyone else
|
||||
|
||||
Reference in New Issue
Block a user