Skip to content

harvey: deliver pipeline — production-style port with verbatim prompts - #1627

Merged
Evanfeenstra merged 14 commits into
mainfrom
harvey-pipeline
Sep 1, 2026
Merged

harvey: deliver pipeline — production-style port with verbatim prompts#1627
Evanfeenstra merged 14 commits into
mainfrom
harvey-pipeline

Conversation

@Evanfeenstra

Copy link
Copy Markdown
Collaborator

Recreates the stakwork Harvey LAB production workflow as vein lab workflows (`mcp/src/lab/harvey`): normalize → register namespace → EvalSet/requirements → per-doc agent graph ingestion → checklist/fact-base/case-law/tailor → drafters + 4 parallel verifiers → aggregator → per-criterion LLM judge → dispute → unified eval-chain persistence → webhook → recursion gate.

Standalone by design: the rubric is a run input and the pipeline self-scores against it — never a harvey-run benchmark candidate (candidates may not see a rubric).

  • Verbatim production prompts as markdown in harvey/prompts/ (14 files), spliced into step configs at seed time via @@include markers; stakwork [$(step).output.*] tokens translated to vein templates, tool names to lab step names, container paths to ./.
  • New steps: 9 harvey pure/plumbing steps, harvey/generate-docx/generate-xlsx (pandoc/openpyxl agent tools), the pinned read-only harvey/graph-sub-agent, jarvis/register-namespace, allow_scratchpad passthrough on the jarvis triplet steps, and a live jarvis/preflight.ts.
  • Vein core: ?. optional chaining in the template language (skipped-step refs), and a one-time nudge when an agent loop ends on a premature text-only turn without final_answer.
  • Shaken down live on employment-labor/draft-updated-anti (6 docs, 60 criteria): full pipeline pass — 60/60 on its own judge, complete eval chain persisted to the graph. Pause → restart → durable-resume exercised three times mid-run; the fixes in this branch (credential-hunting sheets errors, case-law task-goal footer, CriterionResult dedup, ?.) all came from that run.
  • Smokes: deliver-smoke.ts (offline) + jarvis/harvey/sheets smokes + vein tests (556) green; tsc clean. Also fixes latent zod-4 typing breaks surfaced by a fresh install.

🤖 Generated with Claude Code

Evanfeenstra and others added 10 commits August 31, 2026 12:53
The stakwork Harvey LAB workflow recreated as vein workflows: intake +
namespace/EvalSet registration, per-doc agent ingestion (completion-marker
dedupe), checklist/fact-base/case-law/tailor phase, drafter fanout + 4
fall-soft verifiers + aggregator, per-criterion LLM judge + dispute +
unified eval-chain persistence + webhook + recursion gate.

Standalone by design: rubric is a run input — never a harvey-run candidate.
Prompts are params placeholders (PASTE-IN SURFACE markers) pending the
production texts. New steps: 8 harvey plumbing/pure steps, the pinned
read-only harvey/graph-sub-agent, jarvis/register-namespace. Offline smoke:
npx tsx src/lab/harvey/deliver-smoke.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Prompts live as markdown in harvey/prompts/ (14 files), spliced into step
configs at seed time via @@include markers — stakwork [$(step).output.*]
tokens translated to vein templates, tool names to lab step names, container
paths to ./, and the ingestion prompt's docx-MCP/pre-parsed sections adapted
to bash+pandoc. Raw exports kept in notes/harvey_prompts (unneeded ones
deleted).

Enabled by the real prompt contracts: allow_scratchpad passthrough on the
jarvis triplet steps (ScratchpadEntry capture), harvey/generate-docx +
generate-xlsx (pandoc/openpyxl agent tools), case-law secrets via secretsEnv,
persona as drafter/verifier system prompt, and the production score ordering
— record the eval chain BEFORE disputing so dispute agents get CriterionResult
refs (harvey/criterion-refs) for Cause triplets, then write annotations back.
EvalRequirement ids now <task_slug>-<criterion_id>.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tall

preflight.ts runs the seeded jarvis steps against a REAL jarvis
(JARVIS_URL/API_TOKEN from env, throwaway namespace) — the go/no-go check
before a live pipeline run: namespace, ontology types, node create/read,
ingest completion marker, search, HAS_REQUIREMENT triplet.

Also: zod 4 typing fixes (z.record now needs a key schema; parse() returns
unknown) in evolve-loop, both pack-results, and the gaia evolve smoke —
latent since the #1626 zod bump, surfaced by the first fresh node_modules
install; runtime was unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The secret store already holds SERPA_API_KEY + COURTLISTENER_API_KEY —
align the case-law agent's secretsEnv and prompt text to those names
(was GOOGLE_SERPA from the raw stakwork export).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The prompt body references 'the task goal below' but the exported text ends
without it (production's runner appended it) — the agent would otherwise
have to infer the engagement from checklist.md alone. Cross-check/tailor/
ingestion are record-first by design and carry no such dangling reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Promotes the harvey seed-side expandIncludes hack into a spec: a versioned
files/ area in the workspace (publish/append-draft/get/list, content-hash
semantics, ai-stamping) plus @@include expansion inside
publishWorkflowByContent (publish-time, hash covers expanded text, raw
.src.yaml kept for round-tripping, includes provenance in metadata). Kills
the big-prompt problem for the authoring agent; implementation deferred.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Observed live: 'paste the service-account JSON into the vein secrets UI or
mcp env' sent the checklist-writer agent credential-hunting — env greps and
full-disk finds for service-account JSONs (killed mid-run). The throw now
states sheets are UNAVAILABLE in this environment, forbids searching the
filesystem/env for credentials or retrying sibling sheets tools, and says to
proceed with local markdown files. Same note added to the knowledge-stage
writer system prompt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tally)

Observed live: the case-law agent did ~62min of real SerpAPI/CourtListener
research, drafted in /tmp, never wrote its synthesis file, then ended the
loop with a text-only 'now let's copy this' turn — finalAnswer mode ends on
any no-tool turn, so nothing landed in the artifacts dir. The system prompt
now pins the two rules: build ./case-law-research.md in the working dir
incrementally (scratch may use /tmp, the deliverable may not), and never
emit a tool-less message before final_answer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
finalAnswer mode ends the AI SDK tool loop on ANY turn without a tool call —
including a mid-task narration ('now let's copy this…'), observed live
losing a 62-minute research session whose deliverable needed two more tool
calls. The existing salvage (forced no-tools answer turn) can rescue answer
TEXT but never unfinished file work.

classifyFinalAnswerStop distinguishes the cases: budget exhausted → the
existing forced turn; stopped tool-lessly with budget remaining → resume the
REAL tool loop once (remaining budget, min 4 turns, same tools/checkpoint/
step-finish plumbing, session prefixed with the original task prompt) with a
'continue or call final_answer' user message. A second tool-less stop falls
through to the forced turn / last-text fallback. Usage/cost/step counts fold
the continuation in.

Reaches mcp/lab on the next yarn refresh-vein (yarn dev restart).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sult persistence

The first full live run (60/60 pass) died at its FINAL echo step:
{{ post.error }} where post (the webhook step) was when-skipped — skipped
steps resolve undefined in scope and the expression language throws on
property access into undefined. Add ?. optional chaining to vein's template
language (one-hop guard, JS-style; absent idents still throw Undefined
reference to catch typos) and use it for every skippable ref in the deliver
workflows (post, ingest, cross_check, case_law).

Same run also silently dropped ALL 60 CriterionResult graph writes: the
criterion triplets' inline EvalTriggerOutput side carried a bare { id },
missing the batch step's (type + node_data)-keyed dedup cache and then
failing jarvis schema validation ('result' required). All EvalTriggerOutput
sides now share ONE full node_data object → single resolve, cache hits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Evanfeenstra and others added 4 commits September 1, 2026 08:56
A mid-stream connection death killed a whole agent step. Once response
headers are in, the SDK's request-level retry is out of the picture: if the
body then dies, undici raises a bare `TypeError: terminated` and EVERY result
promise on the stream rejects (verified: steps/response/totalUsage/text all
reject), so the old `if (streamError) throw` had nothing left to read and
threw the session away. Seen live — a 34-tool-call case-law research step lost
~12 minutes in, taking its findings with it.

Bank each step as it finishes (`onStepFinish` fires before the error, and
`sf.response.messages` is exactly what a replay needs), then resume: replay the
banked conversation plus a nudge saying the stream was cut and any pending tool
call never ran. Bounded at 5 resumes, with the step budget decremented by work
already done.

`isTransientStreamError` gates it to connection faults only, walking the cause
chain (cycle-safe). Auth/400/schema failures still throw on the first call.
Cancels are excluded by identity via isCancelledError, not by message sniffing:
a vein CancelledError is neither named AbortError nor worded like one, so run
control was previously safe only by luck.

Tested against a local server speaking the Anthropic SSE wire format that
genuinely destroys the socket — the only way to reproduce a transport fault:
work done before the cut survives, usage sums across attempts, the resume
carries the banked calls + task + nudge; persistent severing stops at 5; a 401
fails on call 1.

Also: `$error` gains `cause` (flattened, cycle-safe, depth-capped), and the
harvey onError handlers surface it. The diagnosis for this failure lived only
on the cause — handlers packed `$error.message`, i.e. the bare "terminated" —
so the socket-level reason was discarded at exactly the moment it was needed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Evanfeenstra
Evanfeenstra merged commit e315483 into main Sep 1, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant