API Modernization
Modernize Claude API usage across the pipeline, evaluate and finalize data-architecture decision, reshape cross-call analysis for use-case honing.
Why
Pipeline audit reveals phase-1-era patterns held over from early 2026: Anthropic SDK pinned at 0.39.0 (current is 0.122.0), zero prompt caching on the ~11.6KB static extraction prefix (repaid on every call to date), regex-fence JSON parsing instead of structured outputs, analyze.ts still using a stale pre-pivot prompt (batch-themes.md says "API infrastructure company"), and a latent batch.ts truncation bug (max_tokens 16000 with no stop_reason check). Separately, Mark asked whether JSON-in-git suffices or whether a database is warranted — evaluation shows it does, with clear re-evaluation triggers if conditions change. This cycle modernizes the API surface for cost and quality, records the data-architecture verdict, and absorbs c8's deferred "D2 architecture thread" (items D2.1–D2.3: storage decision, structured outputs + retry, analyze reshape).
Objective
Bring the pipeline to best-of-tech Claude API patterns (SDK upgrade, prompt caching, structured outputs, adaptive-thinking-era models), finalize the data-architecture decision with explicit re-evaluation triggers, reshape cross-call analysis from whole-corpus prompt to a map-reduce pattern (per-use-case + per-company granular views, Opus-5 reduce step for overview), fix known bugs (batch truncation, max_tokens unification), and make the site's natural load-path improvements. Outcome: extraction cost and latency drop, analysis quality rises (use-case-centric signals), development velocity increases (deterministic schema reduces prompt/code friction).
Scope
Workstreams:
- W1 — SDK upgrade + shared client (~half day):
@anthropic-ai/sdk0.39 → 0.122, extendpipeline/model.tswith shared Anthropic client, document model overrides in.env.example. - W2 — Bug fixes (rides with W1): batch.ts truncation guard (stop_reason check), max_tokens unification (batch 16000 → EXTRACT_MAX_TOKENS 32000), SDK retry tuning (rely on built-in maxRetries).
- W3 — Structured outputs (~1 day): new
schemas/output-schema.tsderives JSON Schema programmatically fromvocabularies.tsarrays (no zod), observation variants via anyOf, extract.ts and batch.ts switch to.output_config, eliminate regex fence-parsing. - W4 — Prompt caching (~half day): refactor
pipeline/prompt.tsto split static/dynamic parts (split on## Transcriptmarker), extract.ts addscache_controlon system prompt (≈3.5–4.5K tokens, clears Sonnet-5 minimum), batch.ts opts out (unreliable hit rates). - W5 — analyze.ts map-reduce reshape (~1–1.5 days): replace whole-corpus-in-one prompt with Stage 0 (group observations), Map A (per-use-case via Sonnet-5), Map B (per-company via Sonnet-5), Reduce (overview via Opus-5 with adaptive thinking), write to
data/analysis/{use-cases,companies}/then consolidate todata/analysis.json. - W6 — Site load-path small cut (~half day): call detail page fetches
/data/processed/{id}.jsondirectly, precompute search blobs once at load (not per keystroke).
Out of scope:
- Database migration (decided against; see Data-Architecture Decision below).
- Moving aggregate views to
index.json(rollups need full observation objects). - useCallData type-duplication cleanup (cross-project work, deferred).
Key Changes
- W1 — SDK 0.39 → 0.122;
pipeline/model.tsis now the config layer:MODEL(sonnet-5),ANALYZE_MODEL(opus-5,ANTHROPIC_ANALYZE_MODELoverride),EXTRACT_MAX_TOKENS=32000, sharedclient(maxRetries: 4). Overrides documented in.env.example. Added@types/node—npx tsc --noEmitis now a clean gate (fixed two pre-existing type errors it surfaced). - W2 — batch.ts truncation guard (
stop_reason === 'max_tokens'→ failed-raw dump), max_tokens unified viaEXTRACT_MAX_TOKENS, parse failures dump raw output. Retries rely on SDK built-ins. (nanoid was NOT dead — used by upload-sagetap.ts; c8 note stale.) - W3 (amended in-flight) — structured outputs cannot cover per-call extraction: probing found per-schema API limits (16 union params, 24 optional params, "grammar too large" beyond ~50 object properties; a 42-prop subset passed, 77+ failed even enum-free). Documented in
schemas/output-schema.ts. Extraction keeps prompt-driven JSON +validateCallDataenforcement; the three new analyze stages DO useoutput_config.format(small schemas). Prompt now instructs omit/""over null;stripEmptyStrings()cleans output. - W4 —
buildExtractionPromptreturns{staticPart, dynamicPart}(split at## Transcript); extract.ts caches the static system block. Measured: call 1 cache_write=8,373 tokens; call 2 cache_read=8,373. batch.ts deliberately uncached. Shared_qpoint-context.mdfragment () feeds extract + analyze prompts — kills the stale-pivot prompt bug class (batch-themes.md deleted). - W5 — analyze.ts is map-reduce: Stage 0 grouping → Map A per use case + Map B per company (sonnet-5, structured output, skipped when
sourceCallIdsunchanged;--forceregenerates) → reduce on opus-5 over map outputs + index stats only (bounded input forever). Intermediates indata/analysis/{use-cases,companies}/; overview atdata/analysis.json. New prompts:analyze-use-case.md,analyze-company.md,analyze-overview.md. - W6 (amended in-flight) — detail page fetches per-call enriched files
data/calls/{id}.jsonwritten by build-index (fetching rawprocessed/{id}.jsonwould have dropped sourceType/company-override/actionItem enrichment); search blobs precomputed per data load instead of per keystroke.
Outcome
All six workstreams landed and verified 2026-08-31 (same day): two calls re-extracted with validation PASSED and semantic parity against phase-1 output; cache read confirmed; full map-reduce analyze ran on the 4-call corpus producing the first post-pivot analysis.json (use-case scoreboard, per-company trajectories, 7 recommendations); incremental re-run skips unchanged maps; site builds clean. The no-DB verdict stands recorded above with triggers.
Data-Architecture Decision
Verdict: Stay on git-tracked JSON. No database migration warranted at this scale.
Evidence:
- Corpus growth: 4 calls ≈ 44KB (phase-1 peak 34 calls ≈ 388KB); phase-2 growth trajectory ~15 calls/month → ~1MB in 3 months, ~5MB in 15 months.
- Single writer (Mark), no concurrent multi-user writes, git as write log.
- No relational queries;
build-index.tsfull rebuild is milliseconds even at low hundreds of calls. - Scaling problem (analyze.ts whole-corpus prompt) is an LLM-context problem a DB doesn't solve.
Re-evaluation triggers (revisit if any occur):
- Concurrent multi-user writes on the data (org growth).
- Genuine relational query needs (correlations across dimensions that index + lazy fetch can't express).
- Raw-transcript storage footprint (~1.6MB/month) becoming painful (storage or git clone time).
If ever warranted: c8 plan.md:138 prescribes SQLite before anything heavier; index + lazy per-call fetch before reaching for any DB.