carin: autonomous iterative dev loop #1
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
starsetbyte/Carin!1
Loading…
Reference in a new issue
No description provided.
Delete branch "autonomous-iterative-loop"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Implementation of the autonomous iterative dev loop plan
(
docs/superpowers/plans/2026-07-03-autonomous-iterative-loop.md), builttask-by-task via subagent-driven development (fresh implementer + task
reviewer per task, all findings verified against diffs before merge), then
validated end-to-end against a live scratch instance with real DeepSeek API
calls, then given a final whole-branch review.
All 14 tasks complete, reviewed, and merged with main. Two live smoke-test
pipelines run successfully end-to-end. Final whole-branch review's one
gating issue fixed and re-reviewed.
Done (reviewed, tests passing)
Phase A — reliability fixes
StaleRunsnow honorslast_heartbeat_at, not juststarted_atbackoff_until), priority ordering, per-projectserialization via new
NextQueuedRunwaiting → queuedis now a legal resume transitionPhase B — agent protocol
HeartbeatRun,LinkRunThread, handoff fields onRunParamscarin agent say|block|post-artifact|heartbeatCLI verbsPhase C — personas, threads, protocol-aware spawns
internal/dispatch/personas.go,-agentsflag)buildAgentPrompt),status-aware completion (dispatcher no longer stomps over an agent's own
complete/fail/block)Went through three review-driven fix rounds to get the resume-gating logic
right (crash-retry vs. genuine block, compound block→resume→crash→retry
sequences) plus a fix (found via live testing) for
priorSessionIDreturning the oldest rather than most recent session artifact.
Phase D — iterative pipeline
parent_run_id,iteration,max_iterations,handoff_mode), generalized handoff viaHasChildRun(replacing a fragilecontext_summarystring-scan dedup)processVerdict): approve/revise/iteration-capescalation
carin pipeline-create— one command kicks off the whole loopsmoke test against real pipeline runs
Merge with main
Main had advanced 11 commits past this branch's fork point (knowledge engine,
UI redesign, a GitHub client for commit statuses/PR comments) before this
branch was ready — real conflicts in
cmd/carin/main.goandinternal/dispatch/dispatch.go(both added Dispatcher fields/flags in thesame spots). Resolved additively (commit
c0f7c29) — kept both theGitHub-client wiring and the persona-config wiring. Build/vet/test green
after merge; PR confirmed
mergeable: true.End-to-end smoke test (Task 14 Step 6)
Ran two full pipelines against a scratch instance using a real DeepSeek API
key:
reviewer(revise) → fix → reviewer(hit iteration cap) → escalated to
human inbox. Real files created and committed by the builder persona in a
real sandbox git repo.
correctly. (This run was also meant to test spontaneous
carin agent blockusage from an ambiguous goal; the persona chose todecide autonomously instead rather than blocking — a prompt-tuning gap,
not a code defect; the block/resume mechanism was separately verified
deterministically via direct CLI manipulation: draft→running→block→
waiting→reply→queued→cancelled all worked correctly.)
This live testing found and fixed two real bugs beyond the three review
rounds above:
processHandoffswas seeding the next stage's prompt fromartifacts[0](oldest artifact — always thepi-sessionhash, sinceevery fresh spawn posts that before its real output) instead of the most
recent real-output artifact. Fixed in
22a709d, pre-existing bug(predates this whole plan, git blame
de3c191), directly undermined thehandoff-chain goal.
outerHTMLswap ontoa node that wasn't the partial's own root, nesting duplicate page headers
on every update. Fixed in
18034b4.It also surfaced, empirically, that
parseVerdict's strict suffix-matching(
must end with exactly "VERDICT: approve/revise") never once matchedacross 6 real reviewer completions — models always add trailing prose or
markdown formatting after the token. See below.
Final whole-branch review
Verdict: ready to merge, with one fix —
parseVerdict's strictsuffix-matching (confirmed by the live testing above) meant the pipeline's
clean self-approval path had never actually fired against a real model;
every run fell through to defensive-revise and always terminated via
human escalation instead. Fixed in
915771d: last-occurrenceverdict:scan, markdown/whitespace stripping, prefix match (not
Contains, to avoidfalse-positiving on e.g.
"VERDICT: revise — does not meet the approve bar"), with the safety bias (ambiguity → revise/escalate, never a silentfalse-approve) explicitly preserved and tested. Re-reviewed and approved —
every adversarial case hand-traced plus independent extra hostile inputs,
zero false-approve outcomes.
Non-blocking items explicitly assessed as ship-and-track by the final
review (carried forward, not fixed here):
tick()'s per-project run serialization(real, but requires 2+ concurrent same-project runs, which normal
sequential pipelines never produce). Suggested fix if revisited: fold the
claim into
NextQueuedRunas one atomic SELECT+UPDATE.carin agent block(RolePrompt tuning,see smoke test above).
escalateIterationCap's verdict-artifact-before-notify ordering;dead
internal/web/templates/office.html;processHandoffs'sLimit: 50(latent durability edge at higher throughput); a swallowedGetRunre-fetch error inspawnExternal.Test plan
go build ./...,go vet ./...,go test ./...— green at everycommit, and at the final HEAD (
915771d)reviewer per task, iterating to approval)
DeepSeek-backed scratch instance, both reaching the iteration cap and
escalating correctly; block/resume mechanics verified deterministically
(
parseVerdict), re-reviewed and approvedRemaining (post-merge)
-agents,delete stale
carin.oldin the repo root, update README/AGENTS.md- model: Add RunModeAutonomous='autonomous' to valid run modes - webhook: Dispatch run.assigned event when run enters queued status - store: Add ClaimRun with ErrNotAssigned guard — only assigned agent can claim - api: Add POST /runs/{id}/claim endpoint, ErrNotAssigned→409 mapping - client: Add ClaimRun helper and CLI 'run-claim' subcommand - web: Add autonomous to UI mode select - docs: Update README with new mode, API route, and CLI command - skill: Rewrite carin-client skill with autonomous protocol workflowDispatch loop now lives inside the Carin server process — no cron, no bash scripts, no Hermes scheduler middleman. One goroutine polls every 30s, claims queued runs, spawns agents as subprocesses, and updates the ledger. New model fields (opencode): - Priority, RetryCount, MaxRetries, BackoffUntil, LastHeartbeatAt, IdempotencyKey Dispatcher features: - Priority queue (highest priority first, then oldest) - Stale run recovery (auto-fail/re-queue after 10m no heartbeat) - Retry with exponential backoff (1m→2m→4m→30m cap, max 3 retries) - Agent spawn: hermes-sub (fire-and-forget), pi, opencode, codex - Output capture → artifact posting → status update New API endpoints (pi): - POST /runs/{id}/heartbeat — agents send heartbeats - GET /runs?stale=10m — query stuck runs Schema migration handles SQLite's ALTER TABLE UNIQUE limitation by adding idempotency_key as TEXT then creating a unique index. Agents proven: hermes-sub (research), pi (coding). Opencode deprecated. All built autonomously via Carin runs #17-#26.When a run completes with handoff_agent set, the dispatcher auto-creates a follow-up coding run seeded with the research artifact content. New model fields: - HandoffAgent (string) — agent name for the follow-up run - HandoffPrompt (string) — prompt template; {artifact_summary} is replaced Flow: research completes → dispatcher creates coding run (queued) → next tick picks it up → coding agent processes it. All features now complete for zero-intervention operation.Implements client-side methods wrapping existing server endpoints: - HeartbeatRun: POST /runs/{id}/heartbeat - LinkRunThread: POST /runs/{id}/link-thread - RunParams gains: Priority (already existed), HandoffAgent, HandoffPrompt, HandoffMode All tests pass (new test: TestHeartbeatAndLinkThread). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VV1e8zMXrz6Ny5ktDwZrDLReview:
autonomous-iterative-loopVerdict: Looks good — continue in the morning. Not merging yet, there are 4 tasks remaining. But the core is solid.
Architecture — ✅
The three-layer design is right:
The
NextQueuedRunquery with per-project serialization (NOT IN project_id WHERE status=running) is clever — same-project runs can't collide in the queue.Task 10 — ✅ Correct
The
shouldResumeSession+lastQueuedTransitionFromlogic correctly handles the compound sequence:lastQueuedTransitionFromfinds the most recent queued-transition. After crash retry, that'srunning → queued(fromRetryRun), not the stalewaiting → queued. SoshouldResumeSessionreturns false — spawns fresh, correctly.TestShouldResumeSessionFalseAfterBlockThenCrashRetrycovers exactly this. Good test.Issues
1. TOCTOU race (acknowledged, accepted for now)
tick()spawns goroutines;NextQueuedRunfilters by project in SQL, butClaimRun(the actual status bump torunning) happens inside the goroutine. Two same-project runs can pass the filter before either claims. Consequence is mild (one wastes a claim, other gets it). Track as a follow-up — fix would be moving claim intoNextQueuedRunor a project mutex.2. Swallowed error in status-aware completion
spawnExternal~line 650:If
GetRunerrors, we silently fall through and may overwrite the agent's self-reported status. Add a log:3. Minor:
pi-sessionartifact kindpostArtifacthardcodesArtifactMarkdownfor everything including the session ID hex string. Harmless but untidy. Consider passing kind as a parameter.Remaining (11–14)
parent_run_id,handoff_modeschema + generalized handoffcarin pipeline-createCLISummary
Continue in the morning. Solid foundation.
autonomous-iterative-loopto carin: autonomous iterative dev loop🔍 Carin Code Review
Run #219 completed by
pi-dispatchercba605c4a4267712911188de26e14630
Posted by Carin • View run
🔍 Carin Code Review
Run #219 completed by
pi-dispatcherDispatch Summary — Run #219
Task
Review PR #1 in starsetbyte/Carin (test/pr-pipeline → main, author pyrrha-test).
PR URL: #1
Diff URL: #1.diff
Routing decision
Single dispatch → pi-reviewer (run #223)
Why
Persona chosen
pi-reviewer(deepseek-v4-pro) — the dedicated review persona, capable of fetching the diff, running checks, and issuing a verdict.Run created
The reviewer has been instructed to post its full review as
review.mdand end its completion summary withVERDICT: approveorVERDICT: revise, which Carin's pipeline will parse to drive the next step (merge or fix loop).Not routed
Handoff
Dispatch is complete. The pipeline will pick up run #223, spawn pi-reviewer, and act on its verdict.
Posted by Carin • View run
🔍 Carin Code Review
Run #219 completed by
pi-dispatchercba605c4a4267712911188de26e14630
Posted by Carin • View run
🔍 Carin Code Review
Run #219 completed by
pi-dispatchercba605c4a4267712911188de26e14630
Posted by Carin • View run
🔍 Carin Code Review
Run #219 completed by
pi-dispatchercba605c4a4267712911188de26e14630
Posted by Carin • View run