Skip to the content.

Next-step scoping and issue-creation guard plan

Date: 2026-06-30

This plan hardens the path from “a task finished” to “the right next issue exists and can be dispatched.” The target is not more process around every agent turn. The target is a small, enforceable contract for the contexts where fak or DOS will create or sync GitHub issues by default.

Current evidence

The weak point is producer-side scope. Once a vague issue exists, the dispatch worker can only infer intent from prose and labels. That is where autonomous issue creation needs a guard.

Issue candidate contract

A generated GitHub issue is dispatchable only when the candidate carries these fields, whether they are typed JSON before render or recognizable sections after render:

  1. Stable dedupe key: one source-owned marker so re-runs update, not duplicate.
  2. Parent context: source task, issue, epic, milestone, scorecard row, or signal row, plus why this is the next useful leaf now.
  3. Current state: what is already true, with witness provenance.
  4. In scope: the smallest expected change surface and likely owning lane/path.
  5. Out of scope: the nearby work this issue deliberately says no to.
  6. Done condition: a concrete state change, not “improve” or “investigate”.
  7. Witness: command, file, metric, dos verify, dos commit-audit, or other read-back that can prove the done condition.
  8. Acceptance gate: the package test, docs lint, workflow, smoke, or live probe the worker should run before committing.
  9. Routing metadata: lane, labels, priority, and any path hints the router can confirm.
  10. Boundary/risk notes: private-boundary, GPU/hardware, credentials, cost, human decision, or operator-only evidence.
  11. Closure binding: worker-facing reminder that the resolving commit must cite #N and carry the matching (fak <leaf>) trailer.
  12. Work-unit shape: leaf or step means worker-dispatchable; epic, research, triage-only, or needs-triage means decompose or review first.
  13. Expected step budget: a small integer that tells the worker whether the issue is a one-turn leaf, a multi-step leaf, or accidentally an epic.
  14. Assumptions and confusion risks: the assumptions a worker should verify, and nearby meanings it must not conflate.
  15. Coordination notes: lane leases, dependency order, sibling-file risks, or “no special coordination” so the worker does not guess.
  16. Trigger: the event or threshold that justifies creating/updating the issue.
  17. Batch policy: the cap/grouping/update rule that prevents repeated signals from becoming issue spam.

Default creation policy

Default behavior should be context-specific:

Context Default Create/update only if
fak manage clean Stop handoff validate by default; live GitHub sync only when --task-handoff-live is set handoff is verified done, strict-scoped, public-routeable, and has 1-2 next steps
fak loop drive witnessed completion validate handoff by default; issue sync remains explicit loop witness passed and handoff strict-scope review passed
fak task handoff CLI dry-run by default; --live opt-in candidate contract passes and marker dedupe has been checked
score-signal schedule-live allowed self-test green, cap applied, dedupe/update path proven, regression is measured
bench-signal / gate-signal dry-run scheduled; manual live arm self-test green, cap applied, candidate contract passes
fak maturity route dry-run by default; --live opt-in public-routeable lane, strict issue body, and stable marker dedupe
idea-scout dry-run scheduled until triaged research/idea issues are labeled needs-triage and not dispatchable until scoped

Refuse creation, or mark the candidate non-dispatchable, when any of these are true:

Implementation phases

Phase 1: shared issue-candidate review

Add a Go-owned review surface, preferably internal/issuecontract, with a small JSON input and a CLI shell such as:

fak issue contract --file candidate.json
fak issue contract --from-plan issue-plan.json

The review returns a closed-vocabulary verdict, reasons, and whether the issue is dispatchable, triage_only, update_existing, or refused. New tooling should be Go; legacy Python feeders can shell to the CLI before live gh issue create until they are ported.

Candidate reasons to add to dos.toml:

Phase 2: harden task handoff first

Extend HandoffNextStep with optional typed fields:

Keep backward compatibility by rendering legacy body text, but add a --strict-scope mode that refuses missing typed scope. Then flip the guard Stop hook from shadow to enforce for clean-stop handoffs after the tests and docs land.

Update HandoffIssueBody so the generated issue has stable sections:

Phase 3: gate all automated issue producers

Apply the same review before live creation in:

For Python-era feeders, the near-term gate is a fak issue contract subprocess right before gh issue create/edit. The long-term direction is to port the core issue planning/sync logic to Go subcommands.

Phase 4: make dispatch refuse vague leaves

Teach the router/status surface to distinguish:

Update .github/issue-views.json so default dispatch views exclude triage_only and needs-scope labels once those labels exist.

Phase 5: prove the loop end to end

Acceptance for “true end to end working”:

  1. A guarded worker finishes a witnessed task and writes a strict handoff.
  2. The Stop hook validates the handoff and, in live mode, creates or updates one scoped issue.
  3. fak dispatch route routes the issue to the intended lane without keyword guesswork.
  4. fak dispatch tick renders a worker prompt that preserves the issue’s scope, out-of-scope boundary, witness, and gate.
  5. The worker commits on main with #N and (fak <leaf>).
  6. dos commit-audit grades the commit as diff-witnessed.
  7. The close arm re-verifies the SHA and closes the issue.
  8. dispatch_status reports the issue as witnessed/closed, not self-claimed.

Current status

Verified on 2026-06-30 with:

Test plan

Non-goals