-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
Specstride is three roles communicating only through files under .specstride/gates/. No role
calls another directly; the on-disk gate files are the contract.
orchestrator.sh (derives the current phase N from disk; reads the spec)
│
├─(1) PROPOSER — run a headless coding-agent loop for phase N until it writes
│ .specstride/gates/GATE<N>-EVIDENCE.md (atomically), then the loop exits.
│
├─(2) CRITIC — lib/critic.py reads phase N's acceptance criteria + the evidence,
│ does a read-only grounding pass over the files the evidence cites, and
│ asks an LLM for a strict verdict:
│ APPROVED → writes an empty .specstride/gates/GATE<N>-APPROVED marker
│ REJECTED → writes .specstride/gates/GATE<N>-FEEDBACK.md (the specific gaps)
│
├─(3a) APPROVED → git-checkpoint the workdir, N := N+1, back to (1).
└─(3b) REJECTED → archive the rejected evidence, re-run the proposer for the
SAME phase with the feedback. Bounded by MAX_REJECTS; on exceed, halt
and leave everything on disk for a human.
Literal role names are used everywhere — code, files, flags, env vars. Three scripts, five passes:
| Role | Script | Job |
|---|---|---|
| Orchestrator | orchestrator.sh |
Derives the current phase from GATE* markers, drives proposer↔critic, checkpoints on approval, archives on reject, tracks each phase's unmet-criteria signature, enforces budgets/locks. |
| Proposer | proposer.sh |
Runs a headless coding-agent CLI in a fresh-context loop until phase N's GATE<N>-EVIDENCE.md exists. |
| Critic | lib/critic.py |
Reads criteria + evidence, grounds cited files (read-only, byte budget scaled to the backend's context window), asks the LLM for a nonce-bound verdict. |
| Diagnostician | lib/critic.py --diagnose |
Fires once per NEW unmet-criteria signature: same critic backend, the FULL untruncated cited files, no grounding budget. Writes GATE<N>-HINT.md (CASE: GROUNDING or CASE: REAL-GAP + the fix). Advisory only. SPECSTRIDE_DIAGNOSTICIAN=false disables. |
| Accelerator | proposer.sh --role accelerator |
The attempt right after a new hint: the proposer with a prompt narrowed to the unmet criteria, the feedback, the hint and the evidence to splice. Once per hint, never twice in a row, counts toward MAX_REJECTS; writes GATE<N>-ACCELERATION.md for the next wide pass. SPECSTRIDE_ACCELERATOR=false disables. |
sequenceDiagram
autonumber
actor Human
participant O as orchestrator.sh<br/>(orchestrator)
participant P as proposer.sh<br/>(proposer · coding-agent CLI)
participant A as proposer.sh --role accelerator<br/>(accelerator · same tools, narrowed prompt)
participant C as lib/critic.py<br/>(critic · LLM gate)
participant D as lib/critic.py --diagnose<br/>(diagnostician · same backend, no budget)
participant FS as .specstride/gates/<br/>(on-disk contract)
Human->>O: run -w WORKDIR -s SPECS.md
O->>FS: derive phase N from GATE* markers
Note over O: no stored counter — phase is derived
loop until all phases APPROVED (or halt)
alt attempt right after a NEW diagnostician hint<br/>(once per signature, never twice in a row)
O->>A: narrowed prompt: ONLY the unmet criteria<br/>+ critic feedback + hint (primary instruction)
activate A
A->>FS: read GATE<N>-HINT.md + the archived (rejected) evidence
A->>A: fix ONLY the unmet criteria<br/>(footprint rule: touch just the files they cite)
A->>FS: write GATE<N>-EVIDENCE.md<br/>(previous evidence spliced, atomic)
A-->>O: pass exits (test -f passes)
deactivate A
O->>FS: write GATE<N>-ACCELERATION.md<br/>(files this pass changed)
else ordinary attempt
O->>P: full phase prompt<br/>(+ feedback, hint, acceleration note if present)
activate P
loop until evidence exists
P->>P: read PROGRESS.md, do the work
P->>FS: write GATE<N>-EVIDENCE.md (atomic)
end
P-->>O: loop exits (test -f passes)
deactivate P
end
O->>C: judge phase N (criteria + evidence)
activate C
C->>FS: read-only grounding pass over cited files<br/>(byte budget scaled to the backend's context window)
C->>C: LLM verdict, nonce-bound
alt APPROVED
C->>FS: write GATE<N>-APPROVED (empty marker)
C-->>O: VERDICT nonce: APPROVED
O->>O: git checkpoint · N := N+1
else REJECTED (attempt < MAX_REJECTS)
C->>FS: write GATE<N>-FEEDBACK.md (the gaps)
C-->>O: VERDICT nonce: REJECTED
O->>O: unmet-criteria signature<br/>(task IDs the feedback names, or a prose hash)
alt signature is NEW for this phase
O->>D: diagnose phase N<br/>(full rejection history, same critic backend)
activate D
D->>FS: read the FULL, untruncated cited files<br/>(no grounding budget)
D->>D: classify the stall:<br/>CASE: GROUNDING (restage the proof)<br/>or CASE: REAL-GAP (the concrete fix)
D->>FS: write GATE<N>-HINT.md
D-->>O: hint written (advisory — never approves or rejects)
deactivate D
Note over O,A: next attempt = ACCELERATOR
else same signature as last time
Note over O,P: next attempt = wide PROPOSER<br/>(reads feedback + hint + acceleration note)
end
O->>FS: archive stale evidence<br/>(+ feedback, hint, acceleration note)
else MAX_REJECTS exceeded (accelerator attempts count too)
C-->>O: still REJECTED
O->>Human: halt (exit 2) — arbitrate
end
deactivate C
end
O->>Human: all phases approved (exit 0)
Detection is deterministic, not event-driven. The proposer loop's gate is a plain
test -f .specstride/gates/GATE<N>-EVIDENCE.md. Because that loop has already exited when
control returns to the orchestrator, the orchestrator hands the critic the exact path — no
race, no half-written file, nothing to poll. Evidence is written atomically (temp file +
rename) so the critic never observes a partial write.
There is no counter file. On every start the orchestrator scans the GATE* markers on disk
and resumes at the first phase lacking a GATE<N>-APPROVED. Kill the run anywhere, rerun the
same command, and it continues. --start-phase N overrides. This is what makes the loop
crash-safe (see Hardening).
All Python lives under lib/; the Bash entry points stay at the top level.
| Component | Role |
|---|---|
lib/critic.py |
The critic — grounding pass + LLM verdict + nonce parsing |
lib/specstride_spec.py |
The single spec-parsing source of truth (bash and critic both delegate) — see Spec Formats |
lib/verification_plan.py |
Pre-loop VerificationPlan v1 derivation + test scaffolding |
lib/agent_stream.py |
The proposer's stream-json tap that emits agent_* events |
lib/present.py |
The live presenter (inline timeline + status card) |
lib/ralph_loki_ship.py / lib/ralph_otel_ship.py
|
The two telemetry shippers |
lib/verdict_pins.py |
Verdict-parsing pins/guards |
Next: On-Disk Contract · Hardening