docs: spec for execution pipeline — wires 5 harness components end-to-end
One function, one file (~80 lines + ~60 lines tests). worktree → repo-map → agent task → verify-gate → interrupt-gate → ledger. No deploy, no retry, no new dependencies.
This commit is contained in:
parent
14f2e509f4
commit
e2301e8bd3
|
|
@ -0,0 +1,107 @@
|
|||
# SPEC: fable-agent execution pipeline
|
||||
|
||||
**Date:** 2026-07-04
|
||||
**Status:** draft — pending approval
|
||||
**Author:** harness build session
|
||||
|
||||
## Problem
|
||||
|
||||
Five harness components exist in isolation. None are wired together. The pipeline is the 20% that delivers 80%.
|
||||
|
||||
## Existing components (all built, all tested)
|
||||
|
||||
| Component | File | Lines | Role |
|
||||
|---|---|---|---|
|
||||
| WorktreeManager | `worktree-isolation.ts` | 170 | Create/remove disposable git worktrees |
|
||||
| buildRepoMap / minifiedTree | `repo-mapper.ts` | 95 | Generate absolute repo tree for prompt injection |
|
||||
| runVerifyGate / verifyAndClean | `verify-gate.ts` | 102 | Build → lint → test, short-circuit on build fail, wipe worktree on fail |
|
||||
| humanInterruptGate / requireApproval | `interrupt-gate.ts` | 53 | Stdin TTY approval gate (allow/deny/skip/quit) |
|
||||
| FlatLedger | `flat-ledger.ts` | 77 | JSONL state file outside LLM context |
|
||||
|
||||
**Total: 497 lines of real code, 14 tests, all passing.**
|
||||
|
||||
## What we're building
|
||||
|
||||
**One file: `src/fable5/execution-pipeline.ts` (~80 lines).**
|
||||
|
||||
A single async function that chains the 5 components in order:
|
||||
|
||||
```
|
||||
1. create worktree
|
||||
2. inject repo map into prompt
|
||||
3. (caller runs agent task in worktree)
|
||||
4. verify-gate: build → lint → test
|
||||
5. on fail → wipe worktree, record to ledger, done
|
||||
6. on pass → interrupt-gate: human approval
|
||||
7. on deny → wipe worktree, record to ledger, done
|
||||
8. on allow → record to ledger, return success
|
||||
```
|
||||
|
||||
## Interface
|
||||
|
||||
```typescript
|
||||
export interface PipelineSpec {
|
||||
taskName: string; // becomes worktree branch + ledger session
|
||||
agentPrompt: string; // prompt with repo map injected by pipeline
|
||||
repoRoot: string; // root for buildRepoMap + worktree base
|
||||
buildCmd: string[]; // e.g. ["npm", "run", "-s", "build"]
|
||||
testCmd: string[]; // e.g. ["npm", "run", "-s", "test"]
|
||||
lintCmd?: string[]; // optional
|
||||
runAgent: (cwd: string, prompt: string) => Promise<string>; // caller provides
|
||||
}
|
||||
|
||||
export interface PipelineResult {
|
||||
worktreePath: string | null;
|
||||
repoMapInjected: boolean;
|
||||
verify: VerifyGateResult;
|
||||
approved: boolean;
|
||||
worktreeCleaned: boolean;
|
||||
ledgerEntry: LedgerEntry;
|
||||
durationMs: number;
|
||||
}
|
||||
|
||||
export async function runPipeline(spec: PipelineSpec): Promise<PipelineResult>;
|
||||
```
|
||||
|
||||
## Design decisions (Carmack-style)
|
||||
|
||||
1. **One function, one file.** No class. No factory. No config object with 20 optional fields. One async function with a clear spec.
|
||||
|
||||
2. **`runAgent` is a callback.** The pipeline doesn't know about Pi, subagents, or Claude. Caller provides `async (cwd, prompt) => string`. This keeps the pipeline testable without a live model.
|
||||
|
||||
3. **Ledger records every outcome.** Pass, fail, denied — all go to the JSONL ledger. No hidden state.
|
||||
|
||||
4. **Worktree cleaned on fail OR deny.** Only survives if verify passes AND human approves.
|
||||
|
||||
5. **Repo map injected into prompt automatically.** Caller passes the raw prompt; pipeline prepends the minified tree.
|
||||
|
||||
6. **No deploy.** Pipeline stops at "approved worktree exists." Merging to main is a separate explicit action.
|
||||
|
||||
## What we're NOT building
|
||||
|
||||
- No deploy integration (pipeline output is a verified worktree, not a deploy)
|
||||
- No model routing (caller provides `runAgent`)
|
||||
- No retry loop (one shot, one verification)
|
||||
- No parallel agents (single task, single worktree)
|
||||
- No new dependencies
|
||||
- No config files
|
||||
- No abstractions for "future" pipelines
|
||||
|
||||
## Test plan
|
||||
|
||||
One test file: `execution-pipeline.test.ts` (~60 lines).
|
||||
|
||||
- Mock `runAgent` to return success/fail strings
|
||||
- Mock verify-gate by passing build/test commands that succeed/fail
|
||||
- Assert ledger records correct outcome
|
||||
- Assert worktree cleaned on fail
|
||||
- Assert interrupt-gate default-allow on non-TTY
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] `execution-pipeline.ts` exports `runPipeline`, `PipelineSpec`, `PipelineResult`
|
||||
- [ ] All 291 existing tests still pass
|
||||
- [ ] New pipeline tests pass
|
||||
- [ ] `npx tsc --noEmit` clean
|
||||
- [ ] Total new code: ~80 lines pipeline + ~60 lines tests = ~140 lines
|
||||
- [ ] No new dependencies in package.json
|
||||
Loading…
Reference in New Issue