ADR-driven design

Most projects don’t expose their design records. We do. Every decision that shapes the user-facing surface — what an op is, how doctor verdicts are decided, where plugins live, how packages get versioned — was written down as an Architecture Decision Record before a line of code shipped. The records that gate the v2 release are listed here.

The ADRs themselves live alongside the engine source (which is closed) and are mirrored here in summary form. The goal is not transparency for its own sake — it is so that when you ask “why does Tap v2 work this way,” the answer is one clickthrough deep, not a slack DM to a maintainer.


The v2 anchor — read this first

Unified tap primitive2026-05-03-unified-tap-primitive.md

The single load-bearing ADR. Defines the v2 schema (5 storage entities — TapId, Plan, Run, Intent, Fingerprint), the 11-op closure, the read/write discriminated union, the 4-arm verdict enum, the 5-arm intent state machine. Source-of-truth file is core/types.ts. Everything else is downstream.


The release coordination — what shipped together

Ecosystem v2 launch2026-05-04-ecosystem-v2-launch.md

The cross-repo coordination plan: 5 surfaces flip in one weekend (this docs site, tap-skills repo, plugins, npm packages, brew formula). Decides lockstep release over rolling. Documents the npm deprecate policy, the 30/90/180-day deprecation cadence, and the CI grep gate that catches stale marketing copy.

Plan versioning2026-05-04-plan-versioning.md

Why the npm packages bumped v0.x → v1.0 and not v0.5. Sequential integer schema versioning at the Plan level; semver-major at the package level. The two are deliberately separate axes.

Distribution model2026-05-04-distribution-model.md

Per-author namespace (@taprun/<site>/<name> curated; @<author>/<site>/<name> community). Trust tiering between the two. Why community plans require an explicit --trust flag at install. The CI checks that gate the curated subtree.


The architecture follow-throughs

Plugin runtime model2026-05-04-plugin-runtime-model.md

In-process plugin loading is gone. Plugins are MCP sub-servers — ~/.tap/config/plugins/<name>/manifest.json plus an executable speaking JSON-RPC. Locks the boundary so plugins can’t accidentally import private engine code; opens the door to non-Deno plugin authors.

Forge AI lifecycle2026-05-04-forge-ai-lifecycle.md

Forge as inspect + draft, not “AI generates the whole plan.” Deterministic templates (RSS, JSON-LD, OpenAPI, agents.json, ARIA) cover ~80% of real shapes; AI fills the long tail. Each saved plan carries compiled_by metadata so consumers can distinguish forge AI runs from hand-edits.

Error handling philosophy2026-05-04-error-handling-philosophy.md

The two-arm RuntimeResult<T> ({ kind: "ok" } | { kind: "failed" }) and the rule against silent capability degradation. Plans declare requires.runtime; lint rejects mismatches; runtime pre-flights matchCapability before any op fires.

Auth + multi-user2026-05-04-auth-multi-user.md

Why license is per-machine, why there is no cloud account, how multi-seat plans avoid a phantom user database. Local-first by architecture means the credential never leaves the machine.

Cross-machine intent coordination2026-05-04-cross-machine-intent-coord.md

How write taps deduplicate across machines without a central server. The key CEL expression + intent state machine + per-author namespace combine into a content-addressed, server-free dedup contract.

Followup plan2026-05-04-followup-plan.md

The seven §16 follow-up ADRs, sequenced by dependency. What’s done, what’s next, what was deliberately deferred.


Earlier load-bearing records (still authoritative)

Spec as artifact2026-05-01-spec-as-artifact.md

Why @taprun/spec is a derived artifact of core/types.ts, not a separately authored package. The drift-guard test that fires CI on any divergence.

No silent capability degradation2026-05-02-no-silent-capability-degradation.md

The earliest ancestor of the v2 error-handling rule. Removed string warnings-as-API; replaced ephemeral fallback with explicit kind: "failed". Set the precedent for v2’s invariant-first approach.

op:fetch page-session credentials2026-05-01-op-fetch-page-session.md

The credentials: "deno-host" | "page-session" field on op:fetch. Page-session uses the live browser cookies — credentials never cross a trust boundary. The architectural keystone for “browser automation that runs in your browser.”

Op-grain fingerprint2026-05-01-op-grain-fingerprint.md

PlanFingerprint.op_hashes plus the diffPlanFingerprints helper. The substrate for op-grain heal targeting (cache hits become possible at the op level, not just the plan level).

No new legacy: true on save2026-05-01-no-new-legacy-on-save.md

The drainage terminal state. lintPlan({ isNewSave: true }) rejects plans whose body has legacy: true. Existing legacy taps editing-through still pass; new authoring must compose v2 ops.


Not listed here

A handful of ADRs concern engine internals (heal cache layout, K(Δ) accounting, MCP tool surface budget) and are not user-relevant. They live alongside the source and follow the same naming convention; if you need one cited in a discussion, ping hello@taprun.dev.


How to read these

ADRs follow a fixed shape: Context (what state the world was in) → Decision (what we picked) → Application (where in the codebase the decision lands) → Risks + mitigations. Most are 200–400 lines. The unified-tap-primitive parent is the longest at ~600 lines because it’s the schema spec; the release-coordination ADR is similar length because it’s a cross-repo plan.

If you find a contradiction between an ADR and the running code, the running code is wrong by definition — file an issue.