Harvey Pipeline — Implementation Plan
See pipeline-design.md for the full design rationale.
New Module Dependencies
None. The pipeline is implemented entirely with existing Harvey packages and the Go standard library.
Files to Create
| File | Purpose |
|---|---|
harvey/pipeline.go |
All pipeline logic: arg parsing, file reading, @mention scan, model resolution, confidence extraction, step execution, orchestrator |
harvey/pipeline_test.go |
Unit and integration tests |
Files to Modify
| File | Change |
|---|---|
harvey/commands.go |
Register /pipeline in the command dispatch table |
harvey/terminal.go |
Extend buildCompleter: add /pipeline with
path positions starting at token index 2 |
Implementation Phases
Phase 1 — Argument Parsing
func parsePipelineArgs(root string, args []string) (threshold float64, files []string, err error)- Validate
args[0]matches\d+(\.\d+)?%; parse to float64 in (0, 1] - Remaining tokens are workspace-relative file paths
- Each path is validated with
resolveWorkspacePath(path escape blocked) - Return error on missing files or zero paths
Phase 2 — File Reading and @mention Extraction
func readPipelineFile(root, relPath string) (body string, err error)
func scanAtMention(body string) string // returns "" if none foundreadPipelineFileresolves and reads; enforces workspace boundaryscanAtMentionapplies regex@([\w:.-]+)and returns the first capture group
Phase 3 — Model Resolution
func resolvePipelineClient(a *Agent, mention string) (LLMClient, error)- Empty mention → return
a.Client, nil (no allocation) - Non-empty → construct temporary
AnyLLMClientusing same provider asa.Clientbut with model name = mention - If construction fails or model unavailable → return nil, descriptive error
Phase 4 — Confidence Extraction
func extractConfidence(ctx context.Context, client LLMClient, response string) (score float64, stripped string, method string, err error)- Try
{"confidence": X.X ...}JSON at end of response; strip block - If fail: send follow-up
"Rate your confidence 0.0–1.0. Reply only: CONFIDENCE: <score>"; parse reply - If fail: keyword scan (hedging phrases → 0.30; no hedging → 0.80)
Returns the score, the response with the confidence block removed, the method used (“json”, “followup”, “keyword”), and any error from the follow-up call.
Phase 5 — Step Execution
func runPipelineStep(
ctx context.Context,
a *Agent,
client LLMClient,
messages []Message,
out io.Writer,
stepNum, total int,
filename string,
threshold float64,
) (strippedResponse string, confidence float64, err error)- Append hidden confidence instruction to the last
Usermessage - Update spinner:
[N/total] filename | context X% client.Chat(ctx, messages, out)— streams to terminalextractConfidenceon result- Print per-step result line with ✓ or ✗
- If score < threshold → return error
- Return stripped response for next step
Phase 6 — Pipeline Orchestrator
func cmdPipeline(a *Agent, args []string, out io.Writer) errorparsePipelineArgs— validate threshold and files- For each file:
readPipelineFile→scanAtMention→resolvePipelineClient- Stop on any resolution error before running any steps
- Fountain: open pipeline scene block
- Step 1: messages =
a.History+ user message from file - Step N (N > 1): messages = system prompt + user message (prev response + file body)
runPipelineStepfor each step; stop on error- On success:
a.AddMessage("assistant", finalStrippedResponse) - Fountain:
CUT BACK TO:original scene
Phase 7 — Tab Completion
In buildCompleter switch statement, add:
case "/pipeline":
pathStart = 2 // tokens[1] is confidence %, paths start at index 2Phase 8 — Tests
(pipeline_test.go)
| Test | Covers |
|---|---|
TestParsePipelineArgs_valid |
90%, 85.5%, single and multiple files |
TestParsePipelineArgs_invalid |
Missing %, non-numeric, zero files, path escape |
TestScanAtMention_first |
First @mention wins, ignores later ones |
TestScanAtMention_none |
Returns empty string |
TestExtractConfidence_json |
Well-formed JSON block at end of response |
TestExtractConfidence_followup |
JSON absent, follow-up succeeds |
TestExtractConfidence_keyword |
Both JSON and follow-up fail, keyword scan used |
TestCmdPipeline_singleStep |
One file, confidence met, History updated |
TestCmdPipeline_multiStep |
Three files, all pass, final response appended |
TestCmdPipeline_failAtStep2 |
Confidence fails at step 2, History unchanged |
TestCmdPipeline_mentionUnresolved |
@mention not found, stops before step 1 |
TestCmdPipeline_fileNotFound |
Missing file, stops with error |