Problem
The automatic queue writer is dead in the shipped binary. devagent scout (FR-SCOUT-01, "24/7 scout: research backlog -> PRD -> queue") returns the exit-3 notPortedError for every mode except --replay (internal/cli/actions_scout.go:25-27), while scripts/install-scout-launchagent.sh still installs a LaunchAgent whose ProgramArguments run devagent scout --repo … --interval … — so the 24/7 scout has been failing on every tick, and scout.heartbeat.json goes stale with nothing to explain it.
The deterministic core was ported long ago (BuildScoutPrompt, ExtractScoutPayload, ParseScoutOutput, FallbackTask, heartbeat, single-instance lock in internal/scout/scout.go); only the live cycle is missing — and its two stated blockers (the queue package, the worker runtime) landed since: internal/queue (FR-GO-04 #194) and internal/workers (FR-GO-05).
Consequence for the factory: internal/tracker/tracker.go:1-5 documents the intended triangle "scout writes the queue, builder consumes it", but with the scout leg dead the queue only ever receives rows a human enqueues through POST /dispatch, so the builder lane starves into LLM self-selection (see #355).
Requested change
Port runScoutOnce / runScoutLoop into internal/scout and wire the existing flags (--once, --interval, --worker, --timeout, --repo, --dry-run):
- lock → config (
scout.{worker,model,maxQueued,intervalMinutes}) → queue-depth check before dispatch (dispatch is the paid step) → prompt → workers.GetWorker(name).Spawn → ExtractScoutPayload → ParseScoutOutput → queue.EnqueueTask (task-PRD sidecar included) → heartbeat → release lock.
- An empty
ResultText with a nonzero exit code is an error, never a silent empty payload (the stale-binary class, loops 285-290); unparseable output falls back to the deterministic per-UTC-day FallbackTask id; ErrAlreadyQueued is a dedupe hit, not a failure.
--dry-run runs the documented no-AI-call preview: every step except the enqueue/heartbeat writes, and no worker spawn.
- Remove the stale "live dispatch is NOT ported" claims from
internal/scout/extract.go and scout.go package headers.
Acceptance
devagent scout --once enqueues exactly one row from a canned payload, writes .devagent/prds/<id>.md + scout.heartbeat.json, and a second identical cycle reports deduped with no new row.
- A live foreign
scout.lock holder ⇒ locked, zero writes.
- Queue at
scout.maxQueued ⇒ queue-full and no dispatch.
devagent scout --once --dry-run creates no files; --replay output stays byte-identical.
Problem
The automatic queue writer is dead in the shipped binary.
devagent scout(FR-SCOUT-01, "24/7 scout: research backlog -> PRD -> queue") returns the exit-3notPortedErrorfor every mode except--replay(internal/cli/actions_scout.go:25-27), whilescripts/install-scout-launchagent.shstill installs a LaunchAgent whoseProgramArgumentsrundevagent scout --repo … --interval …— so the 24/7 scout has been failing on every tick, andscout.heartbeat.jsongoes stale with nothing to explain it.The deterministic core was ported long ago (
BuildScoutPrompt,ExtractScoutPayload,ParseScoutOutput,FallbackTask, heartbeat, single-instance lock ininternal/scout/scout.go); only the live cycle is missing — and its two stated blockers (the queue package, the worker runtime) landed since:internal/queue(FR-GO-04 #194) andinternal/workers(FR-GO-05).Consequence for the factory:
internal/tracker/tracker.go:1-5documents the intended triangle "scout writes the queue, builder consumes it", but with the scout leg dead the queue only ever receives rows a human enqueues throughPOST /dispatch, so the builder lane starves into LLM self-selection (see #355).Requested change
Port
runScoutOnce/runScoutLoopintointernal/scoutand wire the existing flags (--once,--interval,--worker,--timeout,--repo,--dry-run):scout.{worker,model,maxQueued,intervalMinutes}) → queue-depth check before dispatch (dispatch is the paid step) → prompt →workers.GetWorker(name).Spawn→ExtractScoutPayload→ParseScoutOutput→queue.EnqueueTask(task-PRD sidecar included) → heartbeat → release lock.ResultTextwith a nonzero exit code is an error, never a silent empty payload (the stale-binary class, loops 285-290); unparseable output falls back to the deterministic per-UTC-dayFallbackTaskid;ErrAlreadyQueuedis a dedupe hit, not a failure.--dry-runruns the documented no-AI-call preview: every step except the enqueue/heartbeat writes, and no worker spawn.internal/scout/extract.goandscout.gopackage headers.Acceptance
devagent scout --onceenqueues exactly one row from a canned payload, writes.devagent/prds/<id>.md+scout.heartbeat.json, and a second identical cycle reportsdedupedwith no new row.scout.lockholder ⇒locked, zero writes.scout.maxQueued⇒queue-fulland no dispatch.devagent scout --once --dry-runcreates no files;--replayoutput stays byte-identical.