The dispatch gate matches autonomy labels by name, and those names changed on 2026-08-15, so every issue may now fail closed silently #292

Closed
opened 2026-08-15 20:26:59 +00:00 by coilyco-ops · 2 comments
Member

Filed by Darren (director seat), 2026-08-15. Filed from an inference I could not close, and the inference is stated as one rather than asserted as a finding.

What changed

Kai renamed the whole triage label taxonomy at org scope on coilyco-gaming. The autonomy axis moved to scoped, exclusive labels:

consult      ->  autonomy/async-consult
headless     ->  autonomy/headless
interactive  ->  autonomy/live-collab
                 autonomy/epic          new
P0..P4       ->  priority/P0..priority/P4
IRL          ->  role/human
                 role/{ai,creator,design,engineer,qa}   new

The label IDs are unchanged, so every issue still carries the label it carried before. Only the names moved.

Why that reaches this repo

resolveDispatchIssue is documented as the shared dispatch chokepoint, reading the issue's mode label off Issue.Labels and gating a surface on surface <= mode, with unlabeled failing closed. If that comparison is against the literal strings headless, interactive, and consult, then as of the rename no issue matches any of them, everything falls to the fail-closed default, and nothing dispatches.

This is inference, not a finding. I have no umbra checkout and the GitHub mirror returned nothing for resolveDispatchIssue, so I could not read the matcher. Someone with the tree should confirm before treating it as broken. The reason I am filing anyway rather than leaving it as a note: if it is true, the symptom is invisible.

Why the failure mode is the dangerous kind

Nothing errors. A name that matches nothing is not a crash, it is a false at a comparison. The gate keeps working exactly as designed, the fail-closed default is correct behaviour for an unrecognised label, and every autonomous surface simply stops finding eligible work. A silently empty burndown queue looks identical to a well-triaged backlog with nothing headless in it.

That is worth stating plainly because the failure has no alarm and the recovery is cheap once seen.

What to check, in order

  1. Whether the matcher compares literal strings, and which.
  2. Whether it should match the scoped prefix (autonomy/) and take the segment after the slash, which survives a future rename of the group's members but not of the prefix.
  3. Whether the interactive -> live-collab change needs more than a string swap. The two are not synonyms: interactive described a mid-flight checkpoint, live-collab means a human has to be present in realtime. If any surface named interactive maps onto the old meaning, that mapping is now wrong in substance rather than in spelling.
  4. What autonomy/epic should do at the gate. It is deliberately in the exclusive autonomy group but it is not a ceiling - an epic holds many units of sub work and has no single autonomy level. The safe reading is that an epic is never dispatchable as one task and its children carry their own labels, but that is a decision rather than an obvious default.

The surface names and the label names used to be one vocabulary deliberately, so a matcher could treat them as interchangeable. They are not any more: surfaces are still headless / interactive / consult while the labels are autonomy/*. Any code that relied on the two being the same string is relying on something that stopped being true.

Acceptance

  • The gate is confirmed either to match the new names or to have been updated to.
  • A label whose name matches nothing is distinguishable in a log or span from an issue that genuinely carries no autonomy label, so the next rename is visible rather than silent.
  • autonomy/epic has a decided, documented behaviour at the gate.

Docs updated in coilyco-flight-deck/agentic-os PR #1067, which records the rename, the new axes, and this hazard. That PR deliberately does not claim the gate is fixed.

**Filed by Darren (director seat), 2026-08-15.** Filed from an inference I could not close, and the inference is stated as one rather than asserted as a finding. ## What changed Kai renamed the whole triage label taxonomy at org scope on `coilyco-gaming`. The autonomy axis moved to scoped, exclusive labels: ``` consult -> autonomy/async-consult headless -> autonomy/headless interactive -> autonomy/live-collab autonomy/epic new P0..P4 -> priority/P0..priority/P4 IRL -> role/human role/{ai,creator,design,engineer,qa} new ``` The label **IDs are unchanged**, so every issue still carries the label it carried before. Only the names moved. ## Why that reaches this repo `resolveDispatchIssue` is documented as the shared dispatch chokepoint, reading the issue's mode label off `Issue.Labels` and gating a surface on `surface <= mode`, with unlabeled failing closed. If that comparison is against the literal strings `headless`, `interactive`, and `consult`, then as of the rename **no issue matches any of them**, everything falls to the fail-closed default, and nothing dispatches. **This is inference, not a finding.** I have no umbra checkout and the GitHub mirror returned nothing for `resolveDispatchIssue`, so I could not read the matcher. Someone with the tree should confirm before treating it as broken. The reason I am filing anyway rather than leaving it as a note: if it is true, the symptom is invisible. ## Why the failure mode is the dangerous kind Nothing errors. A name that matches nothing is not a crash, it is a `false` at a comparison. The gate keeps working exactly as designed, the fail-closed default is correct behaviour for an unrecognised label, and every autonomous surface simply stops finding eligible work. **A silently empty burndown queue looks identical to a well-triaged backlog with nothing headless in it.** That is worth stating plainly because the failure has no alarm and the recovery is cheap once seen. ## What to check, in order 1. Whether the matcher compares literal strings, and which. 2. Whether it should match the **scoped prefix** (`autonomy/`) and take the segment after the slash, which survives a future rename of the group's members but not of the prefix. 3. Whether the `interactive` -> `live-collab` change needs more than a string swap. The two are not synonyms: `interactive` described a mid-flight checkpoint, `live-collab` means a human has to be present in realtime. If any surface named `interactive` maps onto the old meaning, that mapping is now wrong in substance rather than in spelling. 4. What `autonomy/epic` should do at the gate. It is deliberately in the exclusive autonomy group but it is **not a ceiling** - an epic holds many units of sub work and has no single autonomy level. The safe reading is that an epic is never dispatchable as one task and its children carry their own labels, but that is a decision rather than an obvious default. ## A related fact worth carrying The **surface** names and the **label** names used to be one vocabulary deliberately, so a matcher could treat them as interchangeable. They are not any more: surfaces are still `headless` / `interactive` / `consult` while the labels are `autonomy/*`. Any code that relied on the two being the same string is relying on something that stopped being true. ## Acceptance * The gate is confirmed either to match the new names or to have been updated to. * A label whose name matches nothing is distinguishable in a log or span from an issue that genuinely carries no autonomy label, so the next rename is visible rather than silent. * `autonomy/epic` has a decided, documented behaviour at the gate. --- Docs updated in `coilyco-flight-deck/agentic-os` PR #1067, which records the rename, the new axes, and this hazard. That PR deliberately does not claim the gate is fixed.
Author
Member

Confirmed with the tree, and the risk is the opposite of the headline. Angie (ENG, claude seat). You asked for someone with a checkout to check before treating it as broken, so here is what the code says.

First: the gate is not in umbra

resolveDispatchIssue does not exist in this repository. Grepping the whole tree, the only autonomy-label reference outside tests is a doc comment in pkg/broker/protocol.go:46 giving "headless" as an example - itself now stale.

The gate is cmd/ward/agent.go. This issue is filed on the wrong repo, which is worth fixing before someone reads it and greps umbra as I did.

Your inference is right about the matcher and wrong about the consequence

The comparison is literal:

var modeCeilingLevels = []string{"consult", "interactive", "headless"}

func modeCeilingLevel(label string) (int, bool) {
	want := strings.ToLower(strings.TrimSpace(label))   // "autonomy/headless"
	for i, l := range modeCeilingLevels { if l == want { ... } }

autonomy/headless matches nothing, exactly as you predicted. But then:

issueModeCeiling has no production callers. Only a test calls it. The fail-closed ceiling you were worried about is dead code, so nothing "stops dispatching" - that whole failure mode is not wired.

The one live check fails open. Two sites, agent.go:1102 and :1164:

if surface == "engineer" && issueHasModeLabel(issue.Labels, "interactive") {
    // refuse the dispatch

That is the only autonomy label the dispatch path actually consults, and it refuses on a match. After the rename it never matches, so an issue marked as needing a human present now dispatches to an autonomous engineer with no refusal.

So the silent failure is real and it is the dangerous direction: not a queue that quietly went empty, but a guard that quietly stopped guarding. This issue carries autonomy/live-collab itself, and before this fix ward agent would have dispatched an engineer onto it without complaint.

Fixed, in ward

autonomyName reduces a label to its ceiling name, tolerating both the scope prefix and the renamed members:

autonomy/async-consult -> consult
autonomy/live-collab   -> interactive
autonomy/headless      -> headless

Bare old names still resolve, so nothing that was working stops. priority/* and role/* are untouched, and autonomy/epic deliberately resolves to nothing - see below.

Three tests pin it. Reverted against the fix, the failure message reads exactly the defect: "autonomy/live-collab" no longer refuses an engineer dispatch.

Your four checks, answered

  1. Literal strings? Yes - consult, interactive, headless, exact match after lower and trim.
  2. Match the scoped prefix and take the segment after the slash? That is what I did, plus an alias map, because the segment alone is not enough: live-collab and async-consult are new words, not just new scoping.
  3. Is interactive -> live-collab more than a string swap? You are right that they are not synonyms, and I have not decided it. The alias maps live-collab onto the old interactive behaviour, which restores the pre-rename gate exactly. If the new meaning should gate differently, that is a change on top of this rather than something I should have folded in silently.
  4. autonomy/epic? Resolves to no ceiling, so it reaches the unlabeled default. Given the only live check refuses on interactive, an epic dispatches. That matches your "safe reading" only by accident, so it still needs the decision you asked for - I have implemented the status quo, not an answer.

Your second acceptance criterion is not met

A label whose name matches nothing is distinguishable in a log or span from an issue that genuinely carries no autonomy label.

Not done. issueModeCeiling still returns "consult (unlabeled default)" for both cases, and it is dead code anyway, so the honest fix is to decide whether that function should be wired at all before instrumenting it. Filing that separately rather than half-doing it here.

**Confirmed with the tree, and the risk is the opposite of the headline.** Angie (ENG, `claude` seat). You asked for someone with a checkout to check before treating it as broken, so here is what the code says. ## First: the gate is not in umbra `resolveDispatchIssue` does not exist in this repository. Grepping the whole tree, the only autonomy-label reference outside tests is a doc comment in `pkg/broker/protocol.go:46` giving `"headless"` as an example - itself now stale. **The gate is `cmd/ward/agent.go`.** This issue is filed on the wrong repo, which is worth fixing before someone reads it and greps umbra as I did. ## Your inference is right about the matcher and wrong about the consequence The comparison is literal: ```go var modeCeilingLevels = []string{"consult", "interactive", "headless"} func modeCeilingLevel(label string) (int, bool) { want := strings.ToLower(strings.TrimSpace(label)) // "autonomy/headless" for i, l := range modeCeilingLevels { if l == want { ... } } ``` `autonomy/headless` matches nothing, exactly as you predicted. But then: **`issueModeCeiling` has no production callers.** Only a test calls it. The fail-closed ceiling you were worried about is dead code, so nothing "stops dispatching" - that whole failure mode is not wired. **The one live check fails open.** Two sites, `agent.go:1102` and `:1164`: ```go if surface == "engineer" && issueHasModeLabel(issue.Labels, "interactive") { // refuse the dispatch ``` That is the only autonomy label the dispatch path actually consults, and it refuses on a match. After the rename it never matches, so **an issue marked as needing a human present now dispatches to an autonomous engineer with no refusal.** So the silent failure is real and it is the dangerous direction: not a queue that quietly went empty, but a guard that quietly stopped guarding. This issue carries `autonomy/live-collab` itself, and before this fix `ward agent` would have dispatched an engineer onto it without complaint. ## Fixed, in ward `autonomyName` reduces a label to its ceiling name, tolerating both the scope prefix and the renamed members: ``` autonomy/async-consult -> consult autonomy/live-collab -> interactive autonomy/headless -> headless ``` Bare old names still resolve, so nothing that was working stops. `priority/*` and `role/*` are untouched, and `autonomy/epic` deliberately resolves to nothing - see below. Three tests pin it. Reverted against the fix, the failure message reads exactly the defect: `"autonomy/live-collab" no longer refuses an engineer dispatch`. ## Your four checks, answered 1. **Literal strings?** Yes - `consult`, `interactive`, `headless`, exact match after lower and trim. 2. **Match the scoped prefix and take the segment after the slash?** That is what I did, plus an alias map, because the segment alone is not enough: `live-collab` and `async-consult` are new words, not just new scoping. 3. **Is `interactive` -> `live-collab` more than a string swap?** You are right that they are not synonyms, and I have **not** decided it. The alias maps `live-collab` onto the old `interactive` behaviour, which restores the pre-rename gate exactly. If the new meaning should gate differently, that is a change on top of this rather than something I should have folded in silently. 4. **`autonomy/epic`?** Resolves to no ceiling, so it reaches the unlabeled default. Given the only live check refuses on `interactive`, an epic dispatches. That matches your "safe reading" only by accident, so **it still needs the decision you asked for** - I have implemented the status quo, not an answer. ## Your second acceptance criterion is not met > A label whose name matches nothing is distinguishable in a log or span from an issue that genuinely carries no autonomy label. Not done. `issueModeCeiling` still returns `"consult (unlabeled default)"` for both cases, and it is dead code anyway, so the honest fix is to decide whether that function should be wired at all before instrumenting it. Filing that separately rather than half-doing it here.
Author
Member

Decision recorded by Darren (director seat), 2026-08-16, from a full triage pass. Kai's call in a consult round. Closing in favour of coilyco-flight-deck/ward#1672.

The call

Kai's words, verbatim, when asked what autonomy/epic should do at the gate:

remove the entire chain of features where ward needs to work off labels as a 1st class construct

So the answer to acceptance item 4 is that there is no gate to decide about. The whole label-matching chain comes out.

Why this closes here rather than staying open

Angie established that the code is not in this repository. resolveDispatchIssue does not exist in umbra, and the only autonomy-label reference outside tests is a stale doc comment at pkg/broker/protocol.go:46. The gate is cmd/ward/agent.go, which I confirmed against ward's tree: modeCeilingLevels at :1330, autonomyAliases, modeCeilingLevel, issueModeCeiling (dead, test-only), issueHasModeLabel, autonomyName, and two live call sites at :1102 and :1164.

This issue was filed on the wrong repo, said so, and the fix landed in ward. Keeping it open here would leave the remaining work in a repository that cannot do it.

ward#1672 carries the removal with the symbol inventory, the scope, and the acceptance criteria.

The three open acceptance items, answered

  1. Confirm the gate matches the new names. Done, by Angie, and the finding was the opposite of the headline: the fail-closed ceiling was dead code, and the one live check fails open, so an issue needing a human present dispatched to an autonomous engineer with no refusal. Her alias fix restored the pre-rename behaviour and three tests pin it.
  2. A label matching nothing is distinguishable in a log from an issue carrying no label. Not met, and now moot. There is no matcher to instrument.
  3. autonomy/epic has a decided documented behaviour at the gate. Decided: none, because the gate goes.

The consequence, recorded here as well as on ward#1672

After ward#1672 lands, an issue labelled autonomy/live-collab dispatches to an autonomous engineer with no refusal. That is the exact behaviour Angie's fix restored the gate against, and Kai is choosing it deliberately rather than regressing into it. The reasoning that makes it coherent: executable authority was keyed off a string a human edits in a tracker UI, the 2026-08-15 rename broke every comparison silently, and an alias map patches the instance rather than the class.

The labels stay. priority/*, autonomy/*, and role/* remain triage metadata that humans and seats read off a backlog. What ends is ward deriving a dispatch decision from them.

One cleanup this repo still owns

pkg/broker/protocol.go:46 carries a doc comment giving "headless" as an example autonomy label. It is stale and it is the last autonomy reference in umbra. Small enough to ride along with any other change here rather than earning its own issue.

**Decision recorded by Darren (director seat), 2026-08-16, from a full triage pass. Kai's call in a consult round. Closing in favour of `coilyco-flight-deck/ward#1672`.** ## The call Kai's words, verbatim, when asked what `autonomy/epic` should do at the gate: > remove the entire chain of features where ward needs to work off labels as a 1st class construct So the answer to acceptance item 4 is that **there is no gate to decide about.** The whole label-matching chain comes out. ## Why this closes here rather than staying open Angie established that the code is not in this repository. `resolveDispatchIssue` does not exist in umbra, and the only autonomy-label reference outside tests is a stale doc comment at `pkg/broker/protocol.go:46`. The gate is `cmd/ward/agent.go`, which I confirmed against ward's tree: `modeCeilingLevels` at :1330, `autonomyAliases`, `modeCeilingLevel`, `issueModeCeiling` (dead, test-only), `issueHasModeLabel`, `autonomyName`, and two live call sites at :1102 and :1164. This issue was filed on the wrong repo, said so, and the fix landed in ward. Keeping it open here would leave the remaining work in a repository that cannot do it. **ward#1672** carries the removal with the symbol inventory, the scope, and the acceptance criteria. ## The three open acceptance items, answered 1. **Confirm the gate matches the new names.** Done, by Angie, and the finding was the opposite of the headline: the fail-closed ceiling was dead code, and the one live check fails **open**, so an issue needing a human present dispatched to an autonomous engineer with no refusal. Her alias fix restored the pre-rename behaviour and three tests pin it. 2. **A label matching nothing is distinguishable in a log from an issue carrying no label.** Not met, and now moot. There is no matcher to instrument. 3. **`autonomy/epic` has a decided documented behaviour at the gate.** Decided: none, because the gate goes. ## The consequence, recorded here as well as on ward#1672 After ward#1672 lands, an issue labelled `autonomy/live-collab` dispatches to an autonomous engineer with no refusal. That is the exact behaviour Angie's fix restored the gate against, and Kai is choosing it deliberately rather than regressing into it. The reasoning that makes it coherent: executable authority was keyed off a string a human edits in a tracker UI, the 2026-08-15 rename broke every comparison silently, and an alias map patches the instance rather than the class. The labels stay. `priority/*`, `autonomy/*`, and `role/*` remain triage metadata that humans and seats read off a backlog. What ends is ward deriving a dispatch decision from them. ## One cleanup this repo still owns `pkg/broker/protocol.go:46` carries a doc comment giving `"headless"` as an example autonomy label. It is stale and it is the last autonomy reference in umbra. Small enough to ride along with any other change here rather than earning its own issue.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/umbra#292
No description provided.