108 lines
4.0 KiB
Markdown
108 lines
4.0 KiB
Markdown
# 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
|