feat(prd-intake): the PRD becomes an input, and devagent up brings the factory up - #373
Merged
Merged
Conversation
…ctory up The driver had no route from what the operator writes into what it builds. docs/PRD.md is a state document by policy and a dirty one *blocks* every iteration, the deterministic lane was empty so the LLM could only find itself to work on (#355: weeks of loop plumbing), and starting the driver meant assembling SELFBUILD_* exports, a `devagent` on PATH, and `make loop-start` by hand. Three changes, one value: write what you want built, type one word, get tested PRs. internal/prdintake (#370): an OPEN checkbox in docs/PRD.md is an instruction. Each `- [ ] item` becomes a queue row carrying its heading as context, its indented sub-bullets as acceptance criteria, and its section body as the task-PRD sidecar, so the worker reads the spec rather than one bullet; the row id is a content hash, so re-running intake over an unchanged PRD queues nothing and the shipped PR ticks the box it built. `- [x]`, blockquotes, struck lines, plain bullets and fenced examples stay exactly what policy says they are — never work. The driver ingests at iteration head, after the PRD-currency gate (a draft mid-edit is never read as intent) and before the pick, so the iteration that reads your edit is the iteration that builds it (`SELFBUILD_PRD_INTAKE=0` opts out); phase 2a already outranks the tracker, so operator intent now outranks LLM self-selection. `devagent prd-intake --dry-run|--json|--max N` runs the same step by hand. An empty lane is now a named state instead of silent progress: the fallback logs and breadcrumbs `queue-empty` with the refill instructions. `devagent up` / `devagent down` (#371): the prerequisite gate (a missing worker CLI aborts instead of burning tokens), the state/queue dirs, PRD intake, a lane census (pending rows / open PRD items / open selfbuild issues — the number that predicts whether the next ten hours ship product or plumbing), a refusal to start behind a dirty docs/PRD.md (the driver would skip every iteration and say nothing), the daemon, then the detached driver launched from its own executable with SELFBUILD_DEVAGENT_BIN pinned to match — the PATH trap that made a Go-only checkout silently shell out to a stale CLI. It ends in a HEALTH receipt, not a spawn receipt: `up` is the driver's parent, so a driver that halts at its own gate exits 0 and stays a pid that answers any liveness probe (measured: 15 seconds of "healthy" after `max iterations reached`), so `up` reaps its own child and waits until the loop lock AND a phase-naming heartbeat exist, or fails the run with the halt line from the driver's log. `--scout` adds the researcher as a second recorded child; `down` signals recorded pids only (never `pkill -f`, #354); `make up|down` now delegate to it. internal/scout (#372): `runScoutOnce`/`runScoutLoop` are ported at last, so `devagent scout --once/--interval` does its real cycle (lock -> config -> depth check BEFORE the paid dispatch -> prompt -> workers.GetWorker().Spawn -> extract -> parse -> enqueue + task-PRD sidecar -> heartbeat) instead of exiting 3 while the scout LaunchAgent ticks every 30 minutes and the queue writer stays silently dead. `devagent create --scout/--tracker` also stopped baking the deleted `dist/src/cli.js` into plist argv, where cobra would choke on it. Verification: gofmt/go vet/golangci-lint clean; full `go test ./...` green (24 new tests: parser contract incl. fences/blockquotes/struck/nesting, goal shape at the 120-word dispatch cap, ingest idempotency + reword + per-pass cap, same-iteration claim, per-iteration idempotency, off-switch and failure paths, dry-run writes nothing, up's step plan/idempotency/gate refusal/ empty-lane hint/dirty-PRD refusal/both health-receipt directions, down's recorded-pid stop); live smokes end to end — a scratch PRD item queued -> claimed -> published as goals/loop-1.md, `up` printing `driver running (pid 31815) — holds the loop lock, iteration 2, phase queue-empty` and then `✗ driver pid 27840 is not running: exited: max iterations reached`, `down` leaving no surviving process, and `scout --once --dry-run` previewing a cycle with no AI call and no writes. docs/PRD.md grows FR-SIMPLE-07/08 + §12 rows + the status note and footer (and the gap-audit blockquote staged in the tree), docs/SELF-BUILD-LOOP.md gains the one-command section, the step table and the intake policy exception, README gains the driver path, PRD.html regenerated with pandoc, and the 2026-09-14 cold-start research pass that shaped the health receipt is committed so its citations resolve.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The driver had no route from what the operator writes into what it builds.
docs/PRD.mdis a state document by policy — and a dirty one blocks every iteration ([prd-fresh] … operator mid-edit), while an empty deterministic lane left the LLM free to select the loop itself (issue #355: weeks of loop plumbing, not product). Starting the driver on top of that meantSELFBUILD_*exports, adevagenton PATH that a Go-only checkout never installed, andmake loop-startby hand.Three changes, one outcome: write what you want built, type one word, get tested PRs.
What
PRD intake — the PRD becomes an input (#370)
internal/prdintake: an OPEN checkbox is an instruction. Each- [ ] itembecomes a queue row carrying its heading as context, its indented sub-bullets as acceptance criteria, and its section body as the task-PRD sidecar (PrdMarkdown), so the worker reads the spec instead of one bullet. Row id =PRD-<8hex>of the normalized text: re-running over an unchanged PRD queues nothing, and the row's own criterion makes the shipped PR tick the box it built.- [x](shipped state), blockquotes (state notes), struck lines, plain prose bullets, fenced examples. Policy stays intact — the PRD is still a state doc, with one documented exception.internal/loopdriver/intake.goruns it at iteration head, after the PRD-currency gate (a draft is never read as intent) and beforepickIssue/claimQueueTask, so the iteration that reads your edit builds it.SELFBUILD_PRD_INTAKE=0/SELFBUILD_PRD_INTAKE_MAXcontrol it; failures are logged and never fail an iteration.devagent prd-intake --dry-run|--json|--max Nruns the same step by hand.queue-emptyinto events, instead of reading as LLM-driven progress.devagent up/devagent down— one command (#371)Gate → state dirs → PRD intake → lane census → daemon → detached driver → report.
uplaunches the driver from its own executable withSELFBUILD_DEVAGENT_BINpinned to match (the PATH trap), refuses to start behind a dirtydocs/PRD.md(the driver would skip every iteration silently), prints pending rows / open PRD items / openselfbuildissues (the number that predicts whether the next ten hours ship product or plumbing), and is idempotent against a live loop lock.downsignals recorded pids only — neverpkill -f(#354).make up|downare now thin wrappers over both;--scoutadds the researcher as a second recorded child.Green
upmeans a running factory. The first smoke caughtupprinting a healthy pid for 15 s after the driver printedmax iterations reached:upis the driver's parent, so a child that halts at its own gate exits 0 and stays a zombie whose pid answers any liveness probe.upnow reaps its own child and withholds the verdict until the loop lock and a phase-naming heartbeat exist —driver running (pid 31815) — holds the loop lock, iteration 2, phase queue-empty— or fails the run with the halt line read from.selfbuild/logs/.--wait 0skips the proof for scripted starts.The scout writer is alive again (#372)
runScoutOnce/runScoutLoopported: lock → config → queue-depth check before the paid dispatch → prompt →workers.GetWorker().Spawn→ extract → parse →queue.EnqueueTask(+ task-PRD sidecar) → heartbeat.devagent scout --once/--intervalno longer exits 3 while thecom.devagent.scoutLaunchAgent ticks every 30 min against a dead queue writer; an emptyResultTextwith a nonzero exit is an error, never a silent fallback. Also:devagent create --scout/--trackerstopped baking the deleteddist/src/cli.jsinto plist argv, where cobra would choke on it.Verification
gofmt -l,go vet ./...,golangci-lint run(0 issues), fullgo test ./...green — 24 new tests: parser contract (fences, blockquotes, struck, nesting, re-wrap stability), the 120-word dispatch cap, ingest idempotency/reword/per-pass cap/missing-PRD, same-iteration claim + per-iteration idempotency + off-switch + dry-run writes nothing, and bothuphealth-receipt directions plusdown's recorded-pid stop.docs/PRD.mditem →prd-intake(once, then idempotent) →queue list→loop --dry-runclaimed it and published.selfbuild/goals/loop-1.md+ anokledger row;upproved a live driver and refused a dead one with its cause;downstopped a real session-leader pid and left nothing;scout --once --dry-runpreviewed a cycle with no AI call and no writes.Docs
FR-SIMPLE-07/08 + §12 command rows + §21 status note + footer,
docs/SELF-BUILD-LOOP.md(one-command section,upstep table, the intake policy exception,SELFBUILD_PRD_INTAKE*knobs), README "Run the automated driver",docs/PRD.htmlregenerated with pandoc, and the 2026-09-14 cold-start research pass committed so its citations resolve.