design: score-bearing scoped labels + invert label-semantics ownership (aos owns taxonomy, ward gets an alias contract) #626

Open
opened 2026-07-07 03:21:47 +00:00 by coilysiren · 1 comment
Owner

Intent (Kai)

Redesign the priority + autonomy label taxonomy around three shifts. This is a design brief - post a concrete proposal as a comment for Kai to approve before any implementation or forge mutation.

1. Score-bearing scoped labels

Labels carry a numeric score component in the scope path, e.g. priority/P0/S4 (tier P0, score 4) and autonomy/automatic/S4 (mode automatic, score 4). A backlog item's dispatch rank is a combination of its priority-score and autonomy-score - Kai sketched 4 + 4 = 8 - and higher combined score surfaces / dispatches first. exclusive per scope so an issue holds exactly one priority and one autonomy at a time (Forgejo scoped-label exclusivity keys on the scope/ prefix).

2. Invert ownership - aos owns the taxonomy, ward gets a thin alias

ward must stop understanding these label semantics in fine grain. Today it hardcodes the vocabulary as literal strings: modeCeilingLevels (cmd/ward/agent.go:598), backlogTierOrder / backlogModeOrder / backlogModeLane / backlogLanes (cmd/ward/agent_director.go:38-43), plus the triage assignment in agent_director_triage.go. Instead aos owns the pretty/cute names and the score math, and hands ward a stable alias or uuid -> name mapping. ward keys its dispatch ceiling + backlog ranking off that alias/score contract, not raw label strings.

3. Reword the autonomy keys

consult / interactive / headless need semantic rewording (Kai gestured automatic for the top mode). Land the new value names as part of the taxonomy.

Open design questions to resolve in the proposal

  • Combine function - additive (4 + 4 = 8) vs multiplicative (Kai said "multiply" then wrote addition). Pin it, and say what the score axis means (urgency x autonomy = auto-burndown priority?).
  • Score range per axis and how it maps from today's P0-P4 + 3 modes.
  • The aos -> ward contract shape - alias table vs uuid, where authored (aos), where consumed (ward), how delivered. Respect the AGENTS.md config-placement law: config lives at the lowest layer that determines it and is never fetched downward. aos sits ABOVE ward in the gradient (cli-guard < ward-kdl < ward < aos), so "aos provides to ward" needs a clean contract that ward reads without reaching up - resolve this tension explicitly, it is the crux.
  • New autonomy value names - the semantic rewording.
  • Migration - org-label edit --name ... --exclusive renames in place across all 3 orgs and is lossless (preserves every issue association). Order the ward parser change vs the forge rename so the dispatch queue never fails closed mid-migration (ward tolerant of both old + new names during the window).
  • Relationship to #614 (KDL per-verb read/write/admin trifecta) and #619 (default-headless intake + relabel-down discipline) - both touch the same vocabulary.

Not in scope here

  • The trivial issue-label -> direct-tier routing fix (#625) - a separate focused change already re-scoped.

Ask

Advisor: produce a first-pass design proposal (combine function, score model, aos<->ward contract shape, new autonomy names, migration ordering) as a comment for Kai to react to. No code, no forge mutation.

## Intent (Kai) Redesign the priority + autonomy label taxonomy around three shifts. This is a **design brief** - post a concrete proposal as a comment for Kai to approve before any implementation or forge mutation. ### 1. Score-bearing scoped labels Labels carry a numeric score component in the scope path, e.g. `priority/P0/S4` (tier P0, score 4) and `autonomy/automatic/S4` (mode automatic, score 4). A backlog item's dispatch rank is a **combination** of its priority-score and autonomy-score - Kai sketched `4 + 4 = 8` - and higher combined score surfaces / dispatches first. `exclusive` per scope so an issue holds exactly one priority and one autonomy at a time (Forgejo scoped-label exclusivity keys on the `scope/` prefix). ### 2. Invert ownership - aos owns the taxonomy, ward gets a thin alias ward must stop understanding these label semantics in fine grain. Today it hardcodes the vocabulary as literal strings: `modeCeilingLevels` (`cmd/ward/agent.go:598`), `backlogTierOrder` / `backlogModeOrder` / `backlogModeLane` / `backlogLanes` (`cmd/ward/agent_director.go:38-43`), plus the triage assignment in `agent_director_triage.go`. Instead **aos owns the pretty/cute names and the score math**, and hands ward a stable **alias or uuid -> name** mapping. ward keys its dispatch ceiling + backlog ranking off that alias/score contract, not raw label strings. ### 3. Reword the autonomy keys `consult` / `interactive` / `headless` need semantic rewording (Kai gestured `automatic` for the top mode). Land the new value names as part of the taxonomy. ## Open design questions to resolve in the proposal - **Combine function** - additive (`4 + 4 = 8`) vs multiplicative (Kai said "multiply" then wrote addition). Pin it, and say what the score axis means (urgency x autonomy = auto-burndown priority?). - **Score range** per axis and how it maps from today's P0-P4 + 3 modes. - **The aos -> ward contract shape** - alias table vs uuid, where authored (aos), where consumed (ward), how delivered. Respect the AGENTS.md config-placement law: config lives at the lowest layer that determines it and is never fetched downward. aos sits ABOVE ward in the gradient (cli-guard < ward-kdl < ward < aos), so "aos provides to ward" needs a clean contract that ward reads without reaching up - resolve this tension explicitly, it is the crux. - **New autonomy value names** - the semantic rewording. - **Migration** - `org-label edit --name ... --exclusive` renames in place across all 3 orgs and is lossless (preserves every issue association). Order the ward parser change vs the forge rename so the dispatch queue never fails closed mid-migration (ward tolerant of both old + new names during the window). - **Relationship to #614** (KDL per-verb read/write/admin trifecta) and **#619** (default-headless intake + relabel-down discipline) - both touch the same vocabulary. ## Not in scope here - The trivial `issue-label` -> direct-tier routing fix (#625) - a separate focused change already re-scoped. ## Ask Advisor: produce a first-pass design proposal (combine function, score model, aos<->ward contract shape, new autonomy names, migration ordering) as a comment for Kai to react to. No code, no forge mutation.
Member

🔎 ward agent advisor

ward agent advisor --driver claude ran a one-shot standard research pass on this question:

Produce a first-pass design proposal for this issue as a comment for Kai to react to. Resolve the open design questions in the body: (1) the score combine function (additive vs multiplicative) and what the combined score means for auto-burndown ordering, (2) the score model and how today's P0-P4 plus 3 modes map onto it, (3) the aos<->ward contract shape for label semantics - alias vs uuid, authored in aos, consumed in ward, honoring the AGENTS.md config-placement law that config is never fetched downward and aos sits ABOVE ward in the layer gradient (this tension is the crux - address it head on), (4) proposed new semantic names for the autonomy keys replacing consult/interactive/headless, (5) a lossless migration ordering via in-place org-label edit across all 3 orgs that never lets ward's dispatch gate fail closed mid-migration. Reference #614 and #619 where they overlap. No code, no forge mutation - proposal only.


Design proposal: score-bearing scoped labels + inverted taxonomy ownership

This is a first-pass proposal for Kai to react to. No code, no forge mutation. Five decisions below, each with the reasoning and the one real fork I want a ruling on.


Where the vocabulary lives in ward today (the surface we are inverting)

To be concrete about the blast radius, ward hardcodes this vocabulary in exactly three places:

  • modeCeilingLevels = {consult, interactive, headless} at cmd/ward/agent.go:598 - the dispatch ceiling. Unlabeled fails closed to consult; the engineer role refuses anything below headless (exit code 7 mode-ceiling, per docs/agent-dispatch-contract.md).
  • backlogTierOrder = {P0..P4}, backlogModeOrder = {headless, interactive, consult}, backlogModeLane, backlogLanes at cmd/ward/agent_director.go:38-43 - the backlog ranker (rankBacklogIssues) sorts by lane, then tier, then number.
  • The triage assignment in agent_director_triage.go (SCORE=<0-3> MODE=<headless|interactive|consult>) - where the director's LLM pass writes tier + mode.

All three understand the label semantics in fine grain. The inversion makes ward understand only a grammar and a number.


1. Combine function: multiplicative, not additive

Recommendation: dispatch_rank = priority_score × autonomy_score.

Kai wrote 4 + 4 = 8 but said "multiply." I want to pin multiply, and here is why the two axes must not be fungible:

  • Additive is fungible. With addition, a trivial-but-automatable P4 climbs toward a critical P0 just by being autonomous. Urgency and autonomy trade off 1:1, which is wrong - a P4 should never approach a P0 on the strength of being easy to dispatch.
  • Multiplicative gates and amplifies. Autonomy becomes a multiplier on urgency: "how much auto-burndown attention this deserves = how urgent it is × how safely the loop can act on it alone." A P1-automatic can correctly outrank a P0-that-needs-a-human, because the auto-queue's whole job is unattended burndown.
  • The manual tier multiplies to zero. If the least-autonomous mode scores 0, manual work multiplies to 0 and drops out of the auto-dispatch queue into the human lane - which is exactly what today's consult lane already does. So the score subsumes the lane gate instead of duplicating it.

The 4 + 4 = 8 sketch is the additive reading; magnitudes here are ordinal only (rank order is all that matters), so I am not trying to reproduce the literal 8. What matters is the ordering multiply produces, and it is the right one for an auto-burndown queue.

What the combined score means: it is specifically the auto-dispatch rank for the director's headless burndown loop, not a universal importance score. Urgency-without-dispatchability (a P0 the loop cannot safely touch alone) correctly falls to 0 in the auto queue and is surfaced to a human through the manual lane. That is a feature, not a bug - it is the queue saying "escalate, do not auto-run."

2. Score model and mapping from P0-P4 + 3 modes

priority axis  (urgency, strictly positive so it never zeros a dispatchable item)
  priority/P0/S5   priority/P1/S4   priority/P2/S3   priority/P3/S2   priority/P4/S1

autonomy axis  (dispatchability, 0 is the deliberate manual gate)
  autonomy/automatic/S3   autonomy/assisted/S2   autonomy/manual/S0
  • Priority keeps 5 tiers, scored 5..1 so every priority tier is >= 1. Nothing that is dispatchable ever gets a 0 rank from the priority side.
  • Autonomy keeps 3 modes, scored 3 / 2 / 0. Only the manual tier is 0, and that 0 is the gate.
  • The S<n> in the scope path is the raw score, carried inline on the label itself. This is the key move for decision 3.

The one fork I want a ruling on: should a P4-automatic (S1 × S3 = 3) still eventually dispatch when the queue is idle (my recommendation - floor priority at 1), or should P4 park like manual work and never auto-run (then P4 would be S0 and multiply to 0)? I lean dispatchable-when-idle because P4 means "not soon," not "never," but this is a real policy choice and yours to make.

3. The aos <-> ward contract: a grammar, not a fetched table (this is the crux)

The config-placement law says config lives at the lowest layer that determines it and is never fetched downward, and aos sits above ward (cli-guard < ward-kdl < ward < aos). So "aos provides label semantics to ward" reads like a violation: ward reaching up.

Resolution: it is not a fetch in either direction, because the score rides on the label instance, and the label instance is shared external state on the forge - exactly like the issue body ward already reads.

Unpack the tension by splitting "the taxonomy" into two things:

  • The authored definitions - which names exist, what score each carries, the pretty/cute naming, the score math. This is aos's to own. aos authors the label set (names + S<n> values) and documents the grammar.
  • The instance on an issue - the actual string autonomy/automatic/S3 attached to issue #N. This lives on Forgejo, and ward already pulls issue labels off the forge on every dispatch and every backlog refresh.

Ward does not need aos's alias table, uuid map, or any authored artifact at runtime. It reads the score off the label string it already has, by parsing a fixed grammar:

<axis>/<name>/S<score>
  axis  in {priority, autonomy}   (ward knows these two axis keys)
  name  opaque to ward             (ward never enumerates {automatic, assisted, ...})
  S<n>  integer                    (ward reads and multiplies it)

This is the inversion Kai asked for. Ward stops hardcoding {consult, interactive, headless} and {P0..P4} and instead knows only:

  • the two axis keys (priority, autonomy),
  • that a trailing /S<n> is the score,
  • that autonomy score 0 is the dispatch gate and the engineer ceiling is autonomy_score >= <threshold>.

aos owns every pretty name and every number. Rename automatic to autopilot, add a fourth autonomy tier, re-score P2 - zero ward changes, because ward never spelled those names.

Why grammar beats an embedded alias table (the alternative the issue floats):

  • An embedded table (compile aos's name->score map into ward at build time) is the correct way to honor the law if a lookup were needed - authored above, embedded, flowing down, never fetched. But it introduces drift: aos adds a tier, ward's embedded copy is stale until a rebuild, and a stale copy fails closed on an unknown label mid-migration.
  • A uuid contract is worse: uuids are unreadable on the issue UI, and Forgejo scoped-label exclusivity keys on the human-readable scope/ prefix anyway, so you need the readable form regardless.
  • The grammar has no artifact to drift. The score is self-describing on the wire. The only thing both sides must agree on is the grammar shape, which is a cross-repo contract (a wire format), not config - it flows neither up nor down, like agreeing on JSON. Document it once in aos, mirror a TestLabelGrammar... in ward that pins the shape, done.

So the answer to "alias vs uuid": neither - inline score in a scoped-label grammar. aos authors names and numbers; ward parses structure and multiplies. The law is honored because nothing is fetched: the score arrives on the issue, which ward already reads.

4. New autonomy value names

Replacing consult / interactive / headless, keeping the top-mode gesture "automatic":

  • automatic (was headless) - the loop dispatches it start-to-merge with no human in the loop. autonomy_score = 3. This is the engineer-eligible tier.
  • assisted (was interactive) - an agent does the work but a human reviews or gates a step. autonomy_score = 2.
  • manual (was consult) - a human drives; no auto-dispatch. autonomy_score = 0, the gate.

automatic / assisted / manual is a clean triad, honors the "automatic" gesture, and manual reads unambiguously as "no autonomy" - better than consult, which sounded like a conversation rather than a dispatch policy. Alternative if you want the middle tier louder: supervised for assisted. I lean assisted.

5. Migration ordering: parser first, then in-place forge rename, lossless, never fail-closed

The hazard is precise: ward's dispatch gate reads mode labels, and unlabeled fails closed to the lowest ceiling (issueModeCeiling in agent.go). If the forge renames headless -> autonomy/automatic/S3 before ward understands the new grammar, every issue reads as unlabeled, the engineer ceiling refuses every dispatch (exit 7 mode-ceiling), and the auto-burndown halts. So ward must tolerate both vocabularies before any label moves.

Ordered path:

  1. Ward tolerant of both (lands first, ward repo). The parser recognizes the old flat labels (headless / interactive / consult, P0..P4) and the new scoped-scored grammar. Old names map to synthesized scores (headless -> automatic S3, P0 -> S5, etc.) through a small compatibility shim. Deploy the director host. No forge change yet - the queue keeps running on old labels, now read through the new score path. This is the ordering answer: parser change strictly precedes the forge rename.
  2. aos authors the taxonomy. aos publishes the name+score set and the grammar doc. No new labels created on the forge yet.
  3. In-place rename per label, per org. Use ward ops forgejo org-label edit --name autonomy/automatic/S3 --exclusive <org> <id> on each existing label. edit is PATCH /orgs/{org}/labels/{id} and is non-destructive - it preserves every issue association (lossless). Do not create-new + delete-old: delete is the destructive verb and would drop associations. Run across all 3 orgs. Because ward already tolerates both, the instant a label flips ward reads the new grammar, and any not-yet-flipped label still parses via the old path. There is no window where an issue reads as unlabeled, so the gate never fails closed mid-migration. An issue momentarily carrying old-priority + new-autonomy also parses cleanly on both axes.
  4. Set exclusivity carefully. --exclusive on the priority/* and autonomy/* scopes enforces one-per-scope (Forgejo keys exclusivity on the scope/ prefix). Audit first with org-label list - if any issue already carries two priority labels the exclusive flip needs a dedup pass before it is safe.
  5. Drop the compat shim (follow-up, ward repo). After all 3 orgs are flipped, verify via org-label list that no legacy flat names survive, then remove the old-name tolerance from ward in a separate commit. Two-phase: tolerate, migrate, then narrow.

Relationship to #614 and #619

  • #614 (KDL per-verb read/write/admin trifecta). The migration in step 3 rides the admin/write tier org-label edit verb, and the delete verb it deliberately avoids is the destructive admin one. The grammar contract itself does not change the KDL surface - it changes what ward does with the labels it reads - but #614 is the guard tier the rename passes through, so land #614's trifecta before or alongside the migration so org-label edit sits in the right tier.
  • #619 (default-headless intake + relabel-down discipline). Under the new names, "default headless" becomes "default automatic" and "relabel-down" becomes lowering the autonomy score (automatic -> assisted -> manual). #619's intake and this proposal share the exact autonomy vocabulary, so they should agree on the three names in decision 4 before either lands. The engineer ceiling check (agent.go:542, surface <= mode on headless > interactive > consult) becomes autonomy_score >= threshold, which is where #619's relabel-down and this score model meet.

Decisions for Kai to react to

  • Combine function: multiply (recommended) vs add.
  • P4 policy: dispatchable-when-idle (priority floored at S1, recommended) vs park-like-manual (P4 = S0).
  • Autonomy names: automatic / assisted / manual (recommended), or supervised for the middle tier.
  • Contract shape: inline score in a scoped grammar (recommended), confirming you are OK with ward never enumerating the pretty names and aos owning every name + number.
  • Migration: confirm parser-first, then in-place org-label edit ordering, and whether the exclusivity audit (step 4) turns up any multi-priority issues to dedup.

Once you pick, the implementation splits into a ward parser+shim change (this repo), an aos taxonomy authoring pass, and the 3-org forge rename - filed as separate tracked work in that dependency order.


Researched and posted automatically by ward agent advisor --driver claude (ward#179). This is one-shot research, not a carried change - verify before acting on it.

— Claude (she/her), via ward agent

### 🔎 ward agent advisor `ward agent advisor --driver claude` ran a one-shot **standard** research pass on this question: > Produce a first-pass design proposal for this issue as a comment for Kai to react to. Resolve the open design questions in the body: (1) the score combine function (additive vs multiplicative) and what the combined score means for auto-burndown ordering, (2) the score model and how today's P0-P4 plus 3 modes map onto it, (3) the aos<->ward contract shape for label semantics - alias vs uuid, authored in aos, consumed in ward, honoring the AGENTS.md config-placement law that config is never fetched downward and aos sits ABOVE ward in the layer gradient (this tension is the crux - address it head on), (4) proposed new semantic names for the autonomy keys replacing consult/interactive/headless, (5) a lossless migration ordering via in-place org-label edit across all 3 orgs that never lets ward's dispatch gate fail closed mid-migration. Reference #614 and #619 where they overlap. No code, no forge mutation - proposal only. --- ## Design proposal: score-bearing scoped labels + inverted taxonomy ownership This is a first-pass proposal for Kai to react to. No code, no forge mutation. Five decisions below, each with the reasoning and the one real fork I want a ruling on. --- ### Where the vocabulary lives in ward today (the surface we are inverting) To be concrete about the blast radius, ward hardcodes this vocabulary in exactly three places: - `modeCeilingLevels = {consult, interactive, headless}` at `cmd/ward/agent.go:598` - the dispatch ceiling. Unlabeled fails closed to `consult`; the `engineer` role refuses anything below `headless` (exit code `7` mode-ceiling, per `docs/agent-dispatch-contract.md`). - `backlogTierOrder = {P0..P4}`, `backlogModeOrder = {headless, interactive, consult}`, `backlogModeLane`, `backlogLanes` at `cmd/ward/agent_director.go:38-43` - the backlog ranker (`rankBacklogIssues`) sorts by lane, then tier, then number. - The triage assignment in `agent_director_triage.go` (`SCORE=<0-3> MODE=<headless|interactive|consult>`) - where the director's LLM pass writes tier + mode. All three understand the label **semantics** in fine grain. The inversion makes ward understand only a **grammar** and a **number**. --- ### 1. Combine function: multiplicative, not additive **Recommendation: `dispatch_rank = priority_score × autonomy_score`.** Kai wrote `4 + 4 = 8` but said "multiply." I want to pin **multiply**, and here is why the two axes must not be fungible: - **Additive is fungible.** With addition, a trivial-but-automatable P4 climbs toward a critical P0 just by being autonomous. Urgency and autonomy trade off 1:1, which is wrong - a P4 should never approach a P0 on the strength of being easy to dispatch. - **Multiplicative gates and amplifies.** Autonomy becomes a multiplier on urgency: "how much auto-burndown attention this deserves = how urgent it is × how safely the loop can act on it alone." A P1-automatic can correctly outrank a P0-that-needs-a-human, because the auto-queue's whole job is unattended burndown. - **The manual tier multiplies to zero.** If the least-autonomous mode scores 0, manual work multiplies to 0 and drops out of the auto-dispatch queue into the human lane - which is exactly what today's `consult` lane already does. So the score **subsumes** the lane gate instead of duplicating it. The `4 + 4 = 8` sketch is the additive reading; magnitudes here are ordinal only (rank order is all that matters), so I am not trying to reproduce the literal 8. What matters is the ordering multiply produces, and it is the right one for an auto-burndown queue. **What the combined score means:** it is specifically the **auto-dispatch rank** for the director's headless burndown loop, not a universal importance score. Urgency-without-dispatchability (a P0 the loop cannot safely touch alone) correctly falls to 0 in the *auto* queue and is surfaced to a human through the manual lane. That is a feature, not a bug - it is the queue saying "escalate, do not auto-run." ### 2. Score model and mapping from P0-P4 + 3 modes ``` priority axis (urgency, strictly positive so it never zeros a dispatchable item) priority/P0/S5 priority/P1/S4 priority/P2/S3 priority/P3/S2 priority/P4/S1 autonomy axis (dispatchability, 0 is the deliberate manual gate) autonomy/automatic/S3 autonomy/assisted/S2 autonomy/manual/S0 ``` - **Priority** keeps 5 tiers, scored 5..1 so every priority tier is >= 1. Nothing that is dispatchable ever gets a 0 rank from the priority side. - **Autonomy** keeps 3 modes, scored 3 / 2 / 0. Only the manual tier is 0, and that 0 is the gate. - The `S<n>` in the scope path is the raw score, carried **inline on the label itself**. This is the key move for decision 3. **The one fork I want a ruling on:** should a P4-automatic (`S1 × S3 = 3`) still eventually dispatch when the queue is idle (my recommendation - floor priority at 1), or should P4 park like manual work and never auto-run (then P4 would be `S0` and multiply to 0)? I lean **dispatchable-when-idle** because P4 means "not soon," not "never," but this is a real policy choice and yours to make. ### 3. The aos <-> ward contract: a grammar, not a fetched table (this is the crux) The config-placement law says config lives at the lowest layer that determines it and is **never fetched downward**, and aos sits **above** ward (`cli-guard < ward-kdl < ward < aos`). So "aos provides label semantics to ward" reads like a violation: ward reaching up. **Resolution: it is not a fetch in either direction, because the score rides on the label instance, and the label instance is shared external state on the forge - exactly like the issue body ward already reads.** Unpack the tension by splitting "the taxonomy" into two things: - **The authored definitions** - which names exist, what score each carries, the pretty/cute naming, the score math. This is aos's to own. aos authors the label set (names + `S<n>` values) and documents the grammar. - **The instance on an issue** - the actual string `autonomy/automatic/S3` attached to issue #N. This lives on Forgejo, and ward already pulls issue labels off the forge on every dispatch and every backlog refresh. Ward does **not** need aos's alias table, uuid map, or any authored artifact at runtime. It reads the score **off the label string it already has**, by parsing a fixed grammar: ``` <axis>/<name>/S<score> axis in {priority, autonomy} (ward knows these two axis keys) name opaque to ward (ward never enumerates {automatic, assisted, ...}) S<n> integer (ward reads and multiplies it) ``` This is the inversion Kai asked for. Ward stops hardcoding `{consult, interactive, headless}` and `{P0..P4}` and instead knows only: - the two axis keys (`priority`, `autonomy`), - that a trailing `/S<n>` is the score, - that autonomy score 0 is the dispatch gate and the `engineer` ceiling is `autonomy_score >= <threshold>`. aos owns every pretty name and every number. Rename `automatic` to `autopilot`, add a fourth autonomy tier, re-score P2 - **zero ward changes**, because ward never spelled those names. **Why grammar beats an embedded alias table** (the alternative the issue floats): - An embedded table (compile aos's name->score map into ward at build time) is the *correct* way to honor the law if a lookup were needed - authored above, embedded, flowing down, never fetched. But it introduces **drift**: aos adds a tier, ward's embedded copy is stale until a rebuild, and a stale copy fails closed on an unknown label mid-migration. - A uuid contract is worse: uuids are unreadable on the issue UI, and Forgejo scoped-label exclusivity keys on the human-readable `scope/` prefix anyway, so you need the readable form regardless. - The grammar has **no artifact to drift**. The score is self-describing on the wire. The only thing both sides must agree on is the grammar shape, which is a cross-repo *contract* (a wire format), not *config* - it flows neither up nor down, like agreeing on JSON. Document it once in aos, mirror a `TestLabelGrammar...` in ward that pins the shape, done. So the answer to "alias vs uuid": **neither** - inline score in a scoped-label grammar. aos authors names and numbers; ward parses structure and multiplies. The law is honored because nothing is fetched: the score arrives *on the issue*, which ward already reads. ### 4. New autonomy value names Replacing `consult / interactive / headless`, keeping the top-mode gesture "automatic": - **`automatic`** (was `headless`) - the loop dispatches it start-to-merge with no human in the loop. `autonomy_score = 3`. This is the `engineer`-eligible tier. - **`assisted`** (was `interactive`) - an agent does the work but a human reviews or gates a step. `autonomy_score = 2`. - **`manual`** (was `consult`) - a human drives; no auto-dispatch. `autonomy_score = 0`, the gate. `automatic / assisted / manual` is a clean triad, honors the "automatic" gesture, and `manual` reads unambiguously as "no autonomy" - better than `consult`, which sounded like a conversation rather than a dispatch policy. Alternative if you want the middle tier louder: `supervised` for `assisted`. I lean `assisted`. ### 5. Migration ordering: parser first, then in-place forge rename, lossless, never fail-closed The hazard is precise: ward's dispatch gate reads mode labels, and **unlabeled fails closed to the lowest ceiling** (`issueModeCeiling` in `agent.go`). If the forge renames `headless -> autonomy/automatic/S3` *before* ward understands the new grammar, every issue reads as unlabeled, the `engineer` ceiling refuses every dispatch (exit `7` mode-ceiling), and the auto-burndown halts. So ward must tolerate **both** vocabularies before any label moves. Ordered path: 1. **Ward tolerant of both (lands first, ward repo).** The parser recognizes the old flat labels (`headless / interactive / consult`, `P0..P4`) **and** the new scoped-scored grammar. Old names map to synthesized scores (`headless -> automatic S3`, `P0 -> S5`, etc.) through a small compatibility shim. Deploy the director host. No forge change yet - the queue keeps running on old labels, now read through the new score path. **This is the ordering answer: parser change strictly precedes the forge rename.** 2. **aos authors the taxonomy.** aos publishes the name+score set and the grammar doc. No new labels created on the forge yet. 3. **In-place rename per label, per org.** Use `ward ops forgejo org-label edit --name autonomy/automatic/S3 --exclusive <org> <id>` on each existing label. `edit` is `PATCH /orgs/{org}/labels/{id}` and is **non-destructive** - it preserves every issue association (lossless). Do **not** create-new + delete-old: `delete` is the destructive verb and would drop associations. Run across all 3 orgs. Because ward already tolerates both, the instant a label flips ward reads the new grammar, and any not-yet-flipped label still parses via the old path. There is no window where an issue reads as unlabeled, so the gate never fails closed mid-migration. An issue momentarily carrying old-priority + new-autonomy also parses cleanly on both axes. 4. **Set exclusivity carefully.** `--exclusive` on the `priority/*` and `autonomy/*` scopes enforces one-per-scope (Forgejo keys exclusivity on the `scope/` prefix). Audit first with `org-label list` - if any issue already carries two priority labels the exclusive flip needs a dedup pass before it is safe. 5. **Drop the compat shim (follow-up, ward repo).** After all 3 orgs are flipped, verify via `org-label list` that no legacy flat names survive, then remove the old-name tolerance from ward in a separate commit. Two-phase: tolerate, migrate, then narrow. ### Relationship to #614 and #619 - **#614 (KDL per-verb read/write/admin trifecta).** The migration in step 3 rides the **admin/write tier** `org-label edit` verb, and the `delete` verb it deliberately avoids is the destructive admin one. The grammar contract itself does not change the KDL surface - it changes what ward *does with the labels it reads* - but #614 is the guard tier the rename passes through, so land #614's trifecta before or alongside the migration so `org-label edit` sits in the right tier. - **#619 (default-headless intake + relabel-down discipline).** Under the new names, "default headless" becomes "**default automatic**" and "relabel-down" becomes **lowering the autonomy score** (automatic -> assisted -> manual). #619's intake and this proposal share the exact autonomy vocabulary, so they should agree on the three names in decision 4 before either lands. The `engineer` ceiling check (`agent.go:542`, `surface <= mode on headless > interactive > consult`) becomes `autonomy_score >= threshold`, which is where #619's relabel-down and this score model meet. --- ### Decisions for Kai to react to - Combine function: **multiply** (recommended) vs add. - P4 policy: **dispatchable-when-idle** (priority floored at S1, recommended) vs park-like-manual (P4 = S0). - Autonomy names: **automatic / assisted / manual** (recommended), or `supervised` for the middle tier. - Contract shape: **inline score in a scoped grammar** (recommended), confirming you are OK with ward never enumerating the pretty names and aos owning every name + number. - Migration: confirm **parser-first, then in-place `org-label edit`** ordering, and whether the exclusivity audit (step 4) turns up any multi-priority issues to dedup. Once you pick, the implementation splits into a ward parser+shim change (this repo), an aos taxonomy authoring pass, and the 3-org forge rename - filed as separate tracked work in that dependency order. --- Researched and posted automatically by `ward agent advisor --driver claude` (ward#179). This is one-shot research, not a carried change - verify before acting on it. <!-- ward-agent-reply --> <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
coilyco-ops added
P3
and removed
P2
labels 2026-07-10 09:00:41 +00:00
coilyco-ops added
P4
and removed
P3
labels 2026-07-14 06:50:30 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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/ward#626
No description provided.