Workflow
A citation-grounded, human-in-the-loop authoring pipeline. One natural-language ask flows through five stages — each produces exactly one approved artifact, and each stage hard-refuses to start until the upstream artifact is approved. The last stage writes code.
Every stage follows the same shape — context.assemble (deterministic or a coarse handoff to insrc_analyze_step) → N sequential LLM steps → checklist.verify → synthesize — and every claim in every artifact is cited to a step output or an analyze bundle. Ungrounded citations refuse to synthesize. No step is ever skipped to save turns, and no LLM call runs in parallel: accuracy first, cost last.
| Stage | Produces | What it is | openQuestions |
|---|---|---|---|
define | DEF — definition | Frames the Epic: problem statement, non-goals, assumptions, constraints, and 1–10 Stories. | yes |
design.epic | HLD — high-level design | One per Epic: chosen framework, shared contracts, Story boundaries, rollout phases, alternatives considered. | yes |
design.story | LLD — low-level design | One per Story: detailed contract (APIs + data model), error paths, test strategy, migration. | yes |
plan | PLAN — ordered Tasks | A sequenced list of implementable Tasks for a Story. Carries no open questions — the decisions were already made upstream. | none |
build | code + validation | The controller implements each Task and the daemon runs a read-only verdict. | — |
openQuestions list on their body (see src/workflow/artifacts/{define,hld,lld}.ts). PLAN deliberately does not — by plan time every design decision is settled, so a plan is pure sequencing.Artifacts & approval gates
Each artifact is written twice: a human-readable .md under docs/ and the canonical JSON under .insrc/artifacts/. The JSON is the machine contract downstream stages read; the Markdown is what you review.
| Artifact | Doc path | Canonical JSON |
|---|---|---|
| DEF | docs/defines/DEF-<h16>.md | .insrc/artifacts/DEF-<h16>.json |
| HLD | docs/designs/HLD-<h16>.md | .insrc/artifacts/HLD-<h16>.json |
| LLD | docs/designs/LLD-<h16>-<storyId>.md | .insrc/artifacts/LLD-<h16>-<storyId>.json |
| PLAN | docs/plans/PLAN-<epic-slug>-<story-id>.md | .insrc/artifacts/PLAN-<epic-hash>-<story-id>.json |
The gate is hard, not advisory. Every downstream workflow refuses to run against an unapproved (or rejected) upstream artifact. You approve from the CLI:
$ insrc workflow approve docs/defines/DEF-<h16>.md # unlocks design.epic
--no-tracker to opt out for one call.Open-question resolution
DEF, HLD, and LLD each surface the questions the stage could not settle. Resolving them is a two-beat contract:
- Optional at the end of the stage that raised them — you can synthesize with questions still open.
- Mandatory at the start of the next stage — the gate is lifted to stage-start and applies to the immediate-upstream artifact.
unresolvedOpenmust be empty before the next stage proceeds (src/workflow/questions.ts).
For each question you have three moves, recorded by recordResolution as a resolved | ignored | deferred status:
| Move | Meaning | Re-surfaces? |
|---|---|---|
| resolve | Record a decision (optionally a chosen option + rationale). | No — closed. |
| ignore | Leave it to implementer judgment; no decision recorded. | No — closed. |
| defer | Park it for a dedicated review pass. A deferred question is not open, so it never auto-blocks a stage. | Only in the review-deferred flow. |
When you resolve, the daemon generates candidate solution options through the shaper LLM (QuestionWithOptions / QuestionOption), so you pick from grounded choices rather than inventing them. Three insrc_workflow_step phases drive this: start runs the stage-start gate, resolve_question records one resolution, and review_deferred walks every parked question across the Epic's DEF + HLD + all LLDs (src/mcp/workflow-step/phases/{start,resolve-question,review-deferred}.ts).
Hierarchical IDs
Every Epic, Story, and Task carries a single canonical identifier that encodes its place in the hierarchy. The separator between levels is :, and the slug form simply swaps each : for - (toCanonical / toSlug in src/workflow/id.ts):
The date prefix plus 8-hex hash is separator-free, so E<8><8> parses unambiguously. The levels nest: an Epic is E…, a Story is E…:S…, and a Task is E…:S…:T…. This gives a clean two-way Epic → Story → Task relationship — parseWorkflowId reads any string back into its level, and parentId / epicOf walk upward. The minting helpers are epicWorkflowId, storyWorkflowId, and taskWorkflowId. The same ID appears in issue titles and in the stored docs, so a glance at an issue tells you exactly where it sits.
The GitHub tracker
The tracker is opt-in and never stores a token — it shells out to gh, which owns your OAuth session. When enabled, approval-time auto-push turns each approved artifact into a typed, natively-nested GitHub issue (src/workflow/tracker-auto.ts):
| On approval of… | Function | Creates |
|---|---|---|
| HLD | autoPushEpicOnHld | An Epic issue (native issue type Epic). |
| LLD | autoPushStoryOnLld | A Story issue, linked as a native sub-issue of its Epic. |
| PLAN | autoPushTasksOnPlan | One Task issue per PlanTask, each a native sub-issue of its Story. |
The nesting is real GitHub sub-issue linkage via ghLinkSubIssue, not just a label convention. Alongside the issues, the tracker commits the artifact docs and links the committed blobs back from each issue, and can seed a GitHub Projects v2 dashboard so you get Epic → Story → Task drill-down on a board. Issue-type names and Task pushing are configurable per repo via src/workflow/config/github.ts (epicIssueType / storyIssueType / taskIssueType, pushTasks, useMilestones); types fail open to untyped issues when the org lacks them.
Bootstrap the automatable GitHub setup once with:
$ insrc tracker setup # config + labels + issue types
$ insrc tracker setup --project # also create a Projects v2 board
runTrackerSetup (src/workflow/tracker/setup.ts) checks that gh is authenticated, verifies the OAuth scopes it needs (admin:org for issue types, project for the board), writes the resolved config, upserts the fixed tracker labels, ensures the Epic/Story issue types exist, and — behind --project — creates the board. Steps whose prerequisite is missing come back as manual with the exact command to run, so nothing is done behind your back.
~/.insrc/github.json entry, resolveGithubConfig returns { type: 'none' }, auto-push short-circuits, and the manual tracker.* workflows refuse with a clear message. You must explicitly enable it (via insrc tracker setup or by writing the config).The build stage
Build is a controller-driven MCP tool, insrc_build_step, with two phases (src/mcp/build-step/):
- implement — the daemon resolves the Task, runs the admission gate, then hands the controller LLM a templated implement prompt pointed at that Task. The daemon does not edit code here; your controller performs the edits.
- validate — the daemon itself runs a read-only agentic verdict session over the result.
The admission gate is strict: admitBuild (src/workflow/runners/build/admission.ts) requires an approved PLAN for the Story. An unapproved or rejected plan short-circuits to plan-unapproved; only an approved plan reaches the freshness/drift check. Because a PLAN carries no open questions, build inherits the decisions already settled at plan-start rather than re-litigating them.
# controller calls insrc_build_step { phase: 'implement', ref: 'E…:S001:T003' }
▚ admission plan E20260717185807ba:S001 approved · fresh
▚ task T003 add tag index to the query planner
▚ next implement → controller runs the templated prompt
# …controller edits files, then:
# insrc_build_step { phase: 'validate', ref: 'E…:S001:T003' }
▚ verdict read-only session · PASS
Two ways to run
The same five stages run either from your MCP client turn-by-turn, or entirely inside the daemon over the socket. The only visible difference in the output is what meta.model gets stamped with.
| Client-driven | Daemon-driven | |
|---|---|---|
| Surface | insrc_workflow_step MCP tool | workflow.run IPC (Unix socket) |
| Who runs the LLM | Your client (Claude Code / Codex CLI) — every turn stays in your session | The daemon, via its resolved provider (config → client CLI → Ollama) |
| Shape | Multi-turn: start → plan → step → synthesize (plus resolve_question / review_deferred) | Single call that streams progress frames per phase, then a terminal frame |
| Progress | Per tool-call, in-band | Incremental frames streamed over the socket (decompose, plan-ready, …) |
meta.model | 'client' | The resolved provider id (e.g. ollama:qwen3… / claude / codex) |
workflow.run refuses a provider that cannot emit structured output.# in your MCP client:
> Use insrc_workflow_step to define an Epic for "Add tag filtering to todos".
phase=start → decomposer prompt
phase=plan → 4-step plan · context.assemble uses insrc_analyze_step
phase=step ×N → epic.frame → stories.compose → checklist.verify
phase=synthesize → docs/defines/DEF-3f9c…e2.md meta.model: client
$ insrc workflow approve docs/defines/DEF-3f9c…e2.md
approved · design.epic unlocked