Skip to content

[bug] Supervision can be wired on an agent that cannot delegate — the picker offers it and the route answers 201 #354

Description

@serge-ivo

Two halves of supervision that never check each other

Supervision is split across two scopes, correctly:

  • The graph is instance-levelagent_supervision (migration 0060) maps
    supervisor_instance_idsubordinate_instance_id. You wire two of your instances together.
  • The ability is agent-leveldelegate_goal, list_subordinates, subordinate_status,
    check_delegation are supervision registry tools, and an agent only receives them if its
    capabilities.tools declares them (toolNamesFor treats a declared list as authoritative).

Exactly one agent declares them: coder-lead (migrations 0063, 0067).

Nothing joins the halves:

The UI offers the wiring to everyone. TeamworkSection.tsx renders "Agents this one
supervises"
with a live picker on every instance's Settings tab. It is mounted unconditionally from
SettingsTab.tsx:1080. On this account that is 26 instances, 25 of which cannot delegate.

The route agrees with the UI, not with the runtime. POST /v1/instances/:id/supervision
(routes/tools.ts:679-696) checks ownership and then the graph rules — no self-edge, no cycle, one
supervisor per subordinate, depth ≤ 4, fan-out ≤ 12. Every one of those is a real check and they are
right to be there. But it never asks whether the supervisor's agent can delegate at all. It returns
201 and writes the row.

What that produces

A user picks a subordinate on, say, Repo Chat. The picker accepts it, the row appears under "Agents
this one supervises", the API said 201. Then they tell the agent to delegate, and it cannot — the tool
was never in its allowlist, so it does not exist from the model's point of view. The agent's best
available behaviour is to explain that it can't, which reads as the agent being broken rather than the
configuration being impossible.

This is the same shape as #351: a surface reporting a capability the runtime will refuse. There
the tool listing said allowed=true, reason=ok for a write-gated tool; here the supervision editor
and its route both say yes to an edge that can never be used.

Fix

Route — in POST /:id/supervision, resolve the supervisor instance's capabilities and 400 when
its tool set contains no supervision connector tool. The message should name the reason
("this agent cannot delegate — its capabilities declare no supervision tools"), consistent with the
other wiring-time rejections, which are deliberately loud because the human is present. That
sentence is already in migration 0060's header and it applies here.

Console — gate the Supervision half of TeamworkSection on the same fact, from the resolved
capabilities that /v1/instances/my/instances already attaches to each instance. Connections and
Deliveries stay ungated: any agent can emit a fact, and any agent can be a target.

Note the two halves must agree in both directions — a picker that offers nothing is fine, a
picker that offers something the route rejects is not, and neither is a route that accepts what the
picker would have hidden.

Out of scope, but worth naming

Whether the subordinate needs anything declared. It does not today, and probably shouldn't — being
delegated to is not a capability, it is just being an instance. Recording that here so the next person
doesn't add a symmetric check by reflex.

Related: #351 (same class), #183 (the graph), #185 (subordinate executes with its own tools/consent).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcoordinationAgent coordination / delegation primitives

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions