PRD: agent triage surface — labctl issue create, label ops, and close through the tracker seam #3

Closed
opened 2026-07-06 21:12:08 +02:00 by dominik.polakovics · 0 comments

This was generated by AI during triage.

Problem Statement

The maintainer's issue workflow runs on agent skills (triage, to-issues, to-prd): an agent in an interactive instance reads an issue, discusses it, applies triage roles, files new issues broken out of plans, and closes what won't be fixed. Per D10, labctl is a session's only tracker surface — but today it can only view, list, comment, and open a PR/change request. An agent that diagnoses a bug mid-session cannot file the issue it found; an agent running triage cannot move an issue between triage roles, cannot create a missing triage label on a fresh repo, and cannot close a wontfix issue. The workflow dead-ends inside every instance, on both tracker bindings.

Solution

Extend the agent surface end-to-end — labctl → agent API → Tracker seam → both bindings — with the full triage set: issue create (with labels), label add/remove on an issue, label list, idempotent label create ("ensure"), and issue close. Record the revised invariant as ADR-0014: what stays dead is the engine writing tracker state implicitly (claim-is-the-branch is untouched; the in-progress label class stays deleted); a deliberate agent workflow action flows through the tracker seam to whichever backend the repo's tracker binding names — exactly as agent comments and PR/CR creation already do. Operator mutations remain builtin-only.

User Stories

  1. As a maintainer running the to-issues skill in an instance, I want the agent to create issues with the needs-triage label applied at creation, so that a plan becomes dependency-ordered tracker issues without me copy-pasting anything.
  2. As a maintainer running the to-prd skill in an instance, I want the agent to publish a PRD as a tracker issue, so that the design record lands where work is tracked.
  3. As a maintainer running the triage skill, I want the agent to add and remove triage-role labels on an issue, so that issues move through the triage state machine without me touching the tracker UI.
  4. As a maintainer, I want the agent to close an issue after posting its explanation comment, so that both wontfix paths (bug and enhancement) complete without manual cleanup.
  5. As a maintainer adopting the triage workflow on a fresh forge-bound repo, I want the agent to ensure the five triage labels exist before triaging, so that the workflow is self-healing instead of failing on the first label apply.
  6. As a maintainer, I want label creation to be idempotent, so that skills can run the ensure step unconditionally and a retry after a timed-out call is safe.
  7. As a maintainer, I want applying a nonexistent label to fail loudly, so that a typo becomes an error instead of a permanent garbage label.
  8. As a maintainer triaging, I want issue list output to include each issue's labels and creation date, so that the triage buckets (unlabeled / needs-triage / needs-info, oldest first) can be computed from one call.
  9. As an AFK run, I want to file a follow-up issue for a problem discovered mid-run, so that findings outside my claimed issue's scope are not lost when the run is reaped.
  10. As an AFK run on a builtin-bound repo, I want issues and comments I create attributed to the run, so that the maintainer can tell agent-authored tracker content from operator-authored content.
  11. As a maintainer, I want the identical labctl surface in interactive instances and AFK runs, so that skills behave the same everywhere and the run token's repo scope stays the only security boundary.
  12. As an operator of an incogni repo, I want every agent-authored body — issue, comment, PR/CR — stripped of AI attribution trailers before it reaches the tracker, so that the defense-in-depth story has no unsanitized write path.
  13. As an operator, I want the tracker metrics to cover the new operations per binding and result, so that dashboards see triage traffic the same way they see ready-queue and pull traffic.
  14. As an operator, I want the operator web UI's behavior unchanged — forge-bound repos still answer "manage issues on the forge" — so that the revision widens only the agent surface.
  15. As an agent in a freshly seeded session, I want the seeded instructions to document every labctl command including the triage set, so that I discover the surface without probing for subcommands.
  16. As a future contributor, I want ADR-0014 to state the revised invariant and ADR-0009 to point at it, so that the ADR log stays append-only and the never-writes-labels sentence isn't silently rewritten.
  17. As a future contributor, I want the agent brief's pinned labctl command list and the D10 row updated, so that the spec agents build against matches the shipped contract.
  18. As a maintainer, I want close and label operations to work identically on builtin-bound repos, so that repos without a forge get the full triage workflow too.
  19. As an agent, I want mutation failures to come back as the canonical error envelope with the pinned exit codes, so that skills can branch on usage error versus API error without parsing prose.

Implementation Decisions

  • Tracker seam: the interface grows issue create (title, body, labels), issue label add, issue label remove, labels list, and label ensure; issue close (already in the seam) is exposed to agents. Both bindings implement all ops. The instrumentation decorator forwards the new ops into the existing (binding, op, result) metric vocabulary — and per the ADR-0013 rule, decorators must keep forwarding capability interfaces rather than masking them.
  • Forgejo binding: the new ops speak the forge REST API with the repo's server-side forge credential, as today. The client hides Forgejo's labels-are-IDs quirk — name-to-ID resolution happens inside the binding; callers speak label names only. Applying a label that does not exist on the repo is an error, never an implicit create. Existing ADR-0009 pagination and label-verification rules carry over unchanged.
  • Builtin binding: implemented over the existing labels and issue-labels tables — no schema migration. Issue numbers come from the repo row's counter inside the insert transaction, as the built-in tracker already does. Created issues and comments carry run attribution, like comments today. Label ensure relies on the existing per-repo name uniqueness.
  • Agent API contract: new run-token-authenticated, repo-scoped routes for issue create, issue label add, issue label remove, issue close, label list, and label create. Label create is idempotent: creating an existing name succeeds and returns the existing label. All errors use the canonical error envelope.
  • labctl commands: issue create --title --body [--labels], issue label add <n> <labels>, issue label remove <n> <labels>, issue close <n>, label list, label create --name [--color --description]. issue list output gains labels and created-at. Output stays plain parseable text; exit codes stay pinned (0 success, 1 API/HTTP error, 2 usage/configuration error).
  • Close semantics: no closing-comment flag; skills post the explanation comment first, then close — two calls, matching the existing convention.
  • Label color/description: optional at create; the builtin default color applies when omitted, and the forge binding supplies an equivalent default.
  • Incogni sanitization: the body sanitizer widens from PR/CR bodies to every agent-authored body — issue create and comment create included — on incogni repos. The triage skill's disclaimer line is body content, not an attribution trailer: it passes through, and the ops docs state that running the triage workflow against an incogni repo is an operator-level contradiction.
  • Invariant revision (ADR-0014): engine-initiated tracker writes stay dead — claiming is still the branch, no label ever encodes engine state. Deliberate agent workflow actions flow through the seam to the bound tracker. ADR-0009 gets a one-line status pointer; the agent brief's pinned command list and D10 row are updated; the ops metrics table gains the new op values.
  • Authorization: one token kind, one surface, for interactive instances and AFK runs alike; the run token's repo scope is the only boundary. Operator mutations remain builtin-only.
  • Seeding: the seeded session instructions document the new commands and the triage-role vocabulary. Triage labels are still never created at repo add — the ensure op is agent-invoked, so forge label sets are only ever mutated on an invited, deliberate action.

Testing Decisions

  • Good tests exercise external behavior through the seam's interfaces — wire shapes, error envelopes, exit codes, stored rows — never implementation details.
  • Forgejo binding: tested against local HTTP fixtures, the existing precedent for the forge client — including name-to-ID resolution, strict-apply failure on a missing label, and ensure idempotency against the forge's duplicate answer.
  • Builtin binding: tested against a real store fixture — issue numbering inside the transaction, run attribution, label ensure under the uniqueness constraint, close.
  • Agent API: handler-contract tests in the existing style — repo scope from the token, error envelope, sanitization of issue and comment bodies on incogni repos, strict-add failure, idempotent label create.
  • labctl: command-layer tests via the injectable environment — flag parsing, exit codes, list output columns.
  • Instrumentation: a forwarding regression test in the style of the observer-rescope test, proving the decorator forwards the new ops and still forwards capability interfaces.

Out of Scope

  • Operator web UI writes to forge-bound repos — operator mutations stay builtin-only.
  • The GitHub REST binding (fast-follow, #1); the new seam ops must be implemented there when it lands, not before.
  • issue edit and issue reopen — no skill consumes them; add when a consumer exists.
  • Auto-creating labels on apply.
  • Seeding triage labels at repo creation on forges — still rejected; ensure is the invited path.
  • A labctl/server version handshake — the observed "stubbed" failure was a stale deployment, which is an ops action, not product surface.
  • Milestones, assignees, attachments, and issue search.

Further Notes

The session failure that motivated this had two independent causes: the deployed lab predated M5 (its labctl answered reads with a stub), and the triage mutation surface genuinely does not exist at HEAD. Redeploying lab fixes the former today; this PRD is the latter. Forgejo quirks the binding must keep respecting: unknown label names are silently discarded from list filters (client-side verification stays), the per-issue comments endpoint ignores pagination, and pagination terminates on an empty page.

> *This was generated by AI during triage.* ## Problem Statement The maintainer's issue workflow runs on agent skills (triage, to-issues, to-prd): an agent in an interactive instance reads an issue, discusses it, applies triage roles, files new issues broken out of plans, and closes what won't be fixed. Per D10, `labctl` is a session's **only** tracker surface — but today it can only view, list, comment, and open a PR/change request. An agent that diagnoses a bug mid-session cannot file the issue it found; an agent running triage cannot move an issue between triage roles, cannot create a missing triage label on a fresh repo, and cannot close a wontfix issue. The workflow dead-ends inside every instance, on both tracker bindings. ## Solution Extend the agent surface end-to-end — `labctl` → agent API → `Tracker` seam → both bindings — with the full triage set: issue create (with labels), label add/remove on an issue, label list, idempotent label create ("ensure"), and issue close. Record the revised invariant as ADR-0014: what stays dead is the **engine** writing tracker state implicitly (claim-is-the-branch is untouched; the `in-progress` label class stays deleted); a **deliberate agent workflow action** flows through the tracker seam to whichever backend the repo's tracker binding names — exactly as agent comments and PR/CR creation already do. Operator mutations remain builtin-only. ## User Stories 1. As a maintainer running the to-issues skill in an instance, I want the agent to create issues with the `needs-triage` label applied at creation, so that a plan becomes dependency-ordered tracker issues without me copy-pasting anything. 2. As a maintainer running the to-prd skill in an instance, I want the agent to publish a PRD as a tracker issue, so that the design record lands where work is tracked. 3. As a maintainer running the triage skill, I want the agent to add and remove triage-role labels on an issue, so that issues move through the triage state machine without me touching the tracker UI. 4. As a maintainer, I want the agent to close an issue after posting its explanation comment, so that both wontfix paths (bug and enhancement) complete without manual cleanup. 5. As a maintainer adopting the triage workflow on a fresh forge-bound repo, I want the agent to ensure the five triage labels exist before triaging, so that the workflow is self-healing instead of failing on the first label apply. 6. As a maintainer, I want label creation to be idempotent, so that skills can run the ensure step unconditionally and a retry after a timed-out call is safe. 7. As a maintainer, I want applying a nonexistent label to fail loudly, so that a typo becomes an error instead of a permanent garbage label. 8. As a maintainer triaging, I want issue list output to include each issue's labels and creation date, so that the triage buckets (unlabeled / needs-triage / needs-info, oldest first) can be computed from one call. 9. As an AFK run, I want to file a follow-up issue for a problem discovered mid-run, so that findings outside my claimed issue's scope are not lost when the run is reaped. 10. As an AFK run on a builtin-bound repo, I want issues and comments I create attributed to the run, so that the maintainer can tell agent-authored tracker content from operator-authored content. 11. As a maintainer, I want the identical labctl surface in interactive instances and AFK runs, so that skills behave the same everywhere and the run token's repo scope stays the only security boundary. 12. As an operator of an incogni repo, I want every agent-authored body — issue, comment, PR/CR — stripped of AI attribution trailers before it reaches the tracker, so that the defense-in-depth story has no unsanitized write path. 13. As an operator, I want the tracker metrics to cover the new operations per binding and result, so that dashboards see triage traffic the same way they see ready-queue and pull traffic. 14. As an operator, I want the operator web UI's behavior unchanged — forge-bound repos still answer "manage issues on the forge" — so that the revision widens only the agent surface. 15. As an agent in a freshly seeded session, I want the seeded instructions to document every labctl command including the triage set, so that I discover the surface without probing for subcommands. 16. As a future contributor, I want ADR-0014 to state the revised invariant and ADR-0009 to point at it, so that the ADR log stays append-only and the never-writes-labels sentence isn't silently rewritten. 17. As a future contributor, I want the agent brief's pinned labctl command list and the D10 row updated, so that the spec agents build against matches the shipped contract. 18. As a maintainer, I want close and label operations to work identically on builtin-bound repos, so that repos without a forge get the full triage workflow too. 19. As an agent, I want mutation failures to come back as the canonical error envelope with the pinned exit codes, so that skills can branch on usage error versus API error without parsing prose. ## Implementation Decisions - **Tracker seam**: the interface grows issue create (title, body, labels), issue label add, issue label remove, labels list, and label ensure; issue close (already in the seam) is exposed to agents. Both bindings implement all ops. The instrumentation decorator forwards the new ops into the existing (binding, op, result) metric vocabulary — and per the ADR-0013 rule, decorators must keep forwarding capability interfaces rather than masking them. - **Forgejo binding**: the new ops speak the forge REST API with the repo's server-side forge credential, as today. The client hides Forgejo's labels-are-IDs quirk — name-to-ID resolution happens inside the binding; callers speak label names only. Applying a label that does not exist on the repo is an error, never an implicit create. Existing ADR-0009 pagination and label-verification rules carry over unchanged. - **Builtin binding**: implemented over the existing labels and issue-labels tables — no schema migration. Issue numbers come from the repo row's counter inside the insert transaction, as the built-in tracker already does. Created issues and comments carry run attribution, like comments today. Label ensure relies on the existing per-repo name uniqueness. - **Agent API contract**: new run-token-authenticated, repo-scoped routes for issue create, issue label add, issue label remove, issue close, label list, and label create. Label create is idempotent: creating an existing name succeeds and returns the existing label. All errors use the canonical error envelope. - **labctl commands**: `issue create --title --body [--labels]`, `issue label add <n> <labels>`, `issue label remove <n> <labels>`, `issue close <n>`, `label list`, `label create --name [--color --description]`. `issue list` output gains labels and created-at. Output stays plain parseable text; exit codes stay pinned (0 success, 1 API/HTTP error, 2 usage/configuration error). - **Close semantics**: no closing-comment flag; skills post the explanation comment first, then close — two calls, matching the existing convention. - **Label color/description**: optional at create; the builtin default color applies when omitted, and the forge binding supplies an equivalent default. - **Incogni sanitization**: the body sanitizer widens from PR/CR bodies to every agent-authored body — issue create and comment create included — on incogni repos. The triage skill's disclaimer line is body content, not an attribution trailer: it passes through, and the ops docs state that running the triage workflow against an incogni repo is an operator-level contradiction. - **Invariant revision (ADR-0014)**: engine-initiated tracker writes stay dead — claiming is still the branch, no label ever encodes engine state. Deliberate agent workflow actions flow through the seam to the bound tracker. ADR-0009 gets a one-line status pointer; the agent brief's pinned command list and D10 row are updated; the ops metrics table gains the new op values. - **Authorization**: one token kind, one surface, for interactive instances and AFK runs alike; the run token's repo scope is the only boundary. Operator mutations remain builtin-only. - **Seeding**: the seeded session instructions document the new commands and the triage-role vocabulary. Triage labels are still never created at repo add — the ensure op is agent-invoked, so forge label sets are only ever mutated on an invited, deliberate action. ## Testing Decisions - Good tests exercise external behavior through the seam's interfaces — wire shapes, error envelopes, exit codes, stored rows — never implementation details. - **Forgejo binding**: tested against local HTTP fixtures, the existing precedent for the forge client — including name-to-ID resolution, strict-apply failure on a missing label, and ensure idempotency against the forge's duplicate answer. - **Builtin binding**: tested against a real store fixture — issue numbering inside the transaction, run attribution, label ensure under the uniqueness constraint, close. - **Agent API**: handler-contract tests in the existing style — repo scope from the token, error envelope, sanitization of issue and comment bodies on incogni repos, strict-add failure, idempotent label create. - **labctl**: command-layer tests via the injectable environment — flag parsing, exit codes, list output columns. - **Instrumentation**: a forwarding regression test in the style of the observer-rescope test, proving the decorator forwards the new ops and still forwards capability interfaces. ## Out of Scope - Operator web UI writes to forge-bound repos — operator mutations stay builtin-only. - The GitHub REST binding (fast-follow, #1); the new seam ops must be implemented there when it lands, not before. - `issue edit` and `issue reopen` — no skill consumes them; add when a consumer exists. - Auto-creating labels on apply. - Seeding triage labels at repo creation on forges — still rejected; ensure is the invited path. - A labctl/server version handshake — the observed "stubbed" failure was a stale deployment, which is an ops action, not product surface. - Milestones, assignees, attachments, and issue search. ## Further Notes The session failure that motivated this had two independent causes: the deployed lab predated M5 (its labctl answered reads with a stub), and the triage mutation surface genuinely does not exist at HEAD. Redeploying lab fixes the former today; this PRD is the latter. Forgejo quirks the binding must keep respecting: unknown label names are silently discarded from list filters (client-side verification stays), the per-issue comments endpoint ignores pagination, and pagination terminates on an empty page.
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
Cloonar/coding-lab#3
No description provided.