Loom

Type to search across the guides, specification and decision records.

↑↓ navigate ↵ open esc close Pagefind

Specification

Loom Capability Maturity

Generated from @loom/spec (loom kernel-manifest --emit-docs). Do not edit by hand — it is derived from the version constants + capability registry, which are the source of truth.

Levels: L5 ABI guaranteed · L4 Verified & benchmarked · L3 Production-ready · L2 Stable implementation · L1 Experimental implementation · L0 Concept accepted

Levels are evidence-derived, not self-assessed: L5 requires a versioned contract pinned by a conformance test; L4 requires a committed bench; L2/L3 require a passing test suite. spec.test.ts fails if a capability claims evidence it does not have — so a badge can’t rot.

CapabilityLevelPackageEvidence
Branch Registry SPIL5@loom/event-runtimeBRANCH_REGISTRY_SPI_VERSION · test
Compaction Archive SPIL5@loom/event-runtimeARCHIVE_STORE_SPI_VERSION · test · used: packages/workflow-runtime/src/compactionArchive.test.ts — byte-exact union across successive compactions, refusal without an archive, the crash order, crypto-shred of the archived copy
Event Store SPIL5@loom/event-runtimeEVENT_STORE_SPI_VERSION · test · bench
Execution Dispatcher SPIL5@loom/dispatcherDISPATCHER_SPI_VERSION · test
Replay VML5@loom/replay-vmEVENT_SCHEMA_VERSION · test
Sandbox Backend SPIL5@loom/sandboxSANDBOX_BACKEND_SPI_VERSION · test
Snapshot Store SPIL5@loom/snapshot-runtimeSNAPSHOT_STORE_SPI_VERSION · test
Tiered MemoryL5@loom/memoryMEMORY_STORE_SPI_VERSION · test · used: loom-examples: runbook-recall, agent-network
Wire ABIL5@loom/wire-abiWIRE_ABI_VERSION · test
Workflow RuntimeL5@loom/workflow-runtimeKERNEL_ABI_VERSION · test
Analytics & ObservabilityL4@loom/analyticstest · bench
Batched Content ResolutionL4@loom/plugin-contenttest · bench
Cooperative SchedulerL4@loom/schedulertest · bench
Deterministic Simulation TestingL4@loom/dsttest · bench
Per-Tenant Fair SchedulerL4@loom/fair-schedulertest · bench
Projection RuntimeL4@loom/projection-runtimetest · bench
Reasoning RouterL4@loom/reasoningtest · bench
Runtime VerificationL4@loom/verifytest · bench
Blueprint ConformanceL3@loom/blueprint-runtimetest · used: loom-examples: db-observability composes per question and validates its own output
Cancellation, Deadlines & Bounded EffectsL3@loom/workflow-runtimetest · used: plugins/worker-server/src/crossSeamCancel.integration.test.ts — cancellation across the dispatch seam; packages/middleware/src/streamTokenBudget.test.ts — K8.4 mid-stream severance
CompilerL3@loom/compilerCOMPILER_MANIFEST_VERSION · test · used: loom-starter, examples/*
Decision MaterializationL3@loom/analyticstest · used: advisoryPipeline + loom introspect
Document CorporaL3@loom/knowledgetest · used: loom-examples: db-sentinel (a runbook corpus edited mid-year — a March finding replays to March’s guidance after the corpus is emptied), runbook-recall (RAG, migrated off its hand-written chunker/prompt builder)
Live QueryL3@loom/live-querytest · used: loom-examples: child-orchestration waits for a terminal state on the log rather than polling loadState
Tamper-Evident Audit ChainL3@loom/plugin-audit-chaintest · used: loom-examples: mcp-app-release-approval chains its approval log; a test tampers with the approver and verifyChain reports the break
Typed Resume PayloadsL3@loom/workflow-runtimetest · used: loom-examples: all 19 ctx.suspend gate sites
Child-Workflow OrchestrationL2@loom/orchestrationtest
Classification Path Reach (array crossings)L2@loom/classifytest
Conversation LayerL2@loom/conversationtest
Conversation SSE GatewayL2@loom/plugin-conversation-ssetest
Durable Agent LoopL2@loom/agenttest · used: packages/agent/src/agentLoopApproval.test.ts — mid-loop HITL parking before the effect, approved and refused
Effect Mode Boundary (journaled vs ambient)L2@loom/effect-runtimetest
Execution-Lifecycle AuthorizationL2@loom/workflow-runtimetest
Framework-free DOM RendererL2@loom/plugin-renderer-domtest
Learning LifecycleL2@loom/learningtest
MCP Apps ui:// EgressL2@loom/plugin-mcp-apptest
MCP Dynamic DispatchL2@loom/plugin-mcptest
MCP ServerL2@loom/plugin-mcptest
Model Reasoning as Audit EvidenceL2@loom/llmtest
Placement AdaptersL2@loom/dispatcher + workerstest
Planning DepthL2@loom/plannertest
Runner-Body Reach AnalysisL2@loom/compilertest · used: packages/app/src/verify.test.ts — driven through defineConfig, with a control showing the bound
Saga / CompensationL2@loom/sagatest
Sandboxed Reshaping ExecutorL2@loom/plugin-sandboxtest
Shard RebalancingL2@loom/event-runtimetest
Step Identity & Interpreter PatternL2@loom/workflow-runtimetest
Storage Scale-Out DecoratorsL2@loom/event-runtimetest · bench
Tool SearchL2@loom/agenttest · used: packages/agent/src/toolSearch.test.ts - the ranker: bounds, determinism, manipulation resistance, aliases
Typed Failure ChannelsL2@loom/workflow-runtimetest
Unicode Scoring, Prompt Overrides and Schedule Time ZonesL2@loom/coretest · used: packages/planner/src/plannerPrompts.test.ts - the prompt overrides and the always-appended untrusted note
Untrusted-Content Integrity AxisL2@loom/classifytest · used: packages/agent/src/promptBoundary.test.ts + packages/reasoning/src/promptBoundary.test.ts — the agent and reasoning paths, each asserted to fence a tainted tool result and refuse a tainted description; plugins/mcp/src/taintedIngress.test.ts — a real flow whose MCP tool returns an injection payload
GPU WorkerL0——

Notes

  • Sandbox Backend SPI (L5): ADR 0144 — QuickJS (in-process and worker mode) passes the same runSandboxBackendConformance kit a 3P Cloud Run / Firecracker / E2B backend must pass. @loom/isolate stays separate: it runs TRUSTED tool code, this seam runs UNTRUSTED scripts.
  • Tiered Memory (L5): ADR 0090/0091 — search is proven portable by FOUR independent implementations of one kit (in-memory IDF, SQLite FTS5, Postgres tsvector, and the 3P loom-plugin-vector’s cosine similarity), which is what makes the seam a contract rather than a description of an inverted index. ADR 0091 took the SPI to 2.0.0: entry id (REQUIRED to round-trip, so the read-filter-replace fallback has something to match) plus the optional remove, because search ranks within one key while a document — the unit a corpus is actually edited in — is a subset of it. MAJOR because a backend that decomposes an entry into columns drops a field nobody told it to keep, which is silent in every case that only checks content. L3 → L5 once MEMORY_STORE_SPI_VERSION landed: the level bar is a versioned contract pinned by a conformance kit, and this seam is the first whose implementations live OUTSIDE this repo, which is what made an unversioned contract an actual hazard rather than a formality. As with every SPI here, nothing forces the constant to be bumped when the shape changes — the version is a declaration, the kit is the check.
  • Batched Content Resolution (L4): TS-10 (ADR 0062) — the read-side sequel to ADR 0059’s putStream. Optional-method + conformance-skip idiom; interactive budget p95 ≤ 150ms for ≤ 50 refs (asserted in batchedResolution.test.ts against an injected-latency backend).
  • Deterministic Simulation Testing (L4): Test/dev-only harness, not a wire contract → cannot be L5; L4 via its append-overhead bench. Production overhead is structurally zero (no runtime package imports it).
  • Per-Tenant Fair Scheduler (L4): Implements the existing Scheduler interface (drops into any consumer); L4 via its Infinity-fast-path bench. Not L5 — the scheduler is ephemeral, not a durable/wire contract.
  • Runtime Verification (L4): No ABI version constant (it is a tool, not a wire contract) → cannot be L5; L4 via its zero-overhead bench.
  • Blueprint Conformance (L3): ADR 0088 (W1a–W1e); ADR 0094 adds the cross-tree half — preservesNodes / blueprintNodeIds, which report which node ids a structural change DROPPED, the failure a single-tree validator cannot see. Its second consumer is the a2ui bridge’s identityDiff, and being a real one changed the API: id sets rather than blueprints, because A2UI components are a flat list. Pure functions over JSON-serializable values — no syscall, no event type, KERNEL_ABI/EVENT_SCHEMA untouched (P10). The catalogue vocabulary is the APP’s (the ADR 0084 pattern); builtinCatalog is loom’s own eight widgets, not a mandate. catalogCoverage is wired into plugins/renderer-react’s suite and immediately found the live gap it was written for — WidgetKind declared 8 kinds, defaultRegistry registered 5, and a test ASSERTED the placeholder string, pinning it as intended. The same hole existed twice on the a2ui egress path — the bridge stamped a catalogId it never checked, and callFunction was never compared to the catalogue’s declared functions; both are now refusals, and recompose() finally produces the updateComponents message whose payload type shipped with no producer. Also W1d (optional stable id on all 8 builders — the derived id re-identifies a node on every prop edit) and W1e (by provenance on SurfacePatchPayload + surfaceProvenanceProjection; SURFACE_PATCH is a userspace event so EVENT_SCHEMA is untouched, and the bridge never forwards by to a viewer). L2 → L3 once db-observability called it: the one example that COMPOSES a blueprint per question, from an agent’s widget selection, with nothing looking at the result before a renderer does. It validates clean on all four topics — so the evidence is the negative case, a test that reuses a topic’s primary widget as a WHY widget and gets duplicate-node-id (ids are derived from content, so the same entry built twice collides on the renderer’s key and one silently replaces the other). Without that, a validator wired to nothing looks identical to one that passes. ADR 0095 extends the same surface to the DATA axis: the address freezes a binding’s NAME while the projection behind it is unversioned live code, so a declared version is compared by checkVersionPins and the recorded pin rides in Blueprint.projectionVersions, INSIDE the content address. Its second consumer is ReadModelService.checkPins — an unversioned extractor is a live defect there, not a hypothetical, and that is the half an example now exercises: db-observability’s PROFILE_MODEL declares a version, the journaled profile carries evidenceVersion beside the catalogueVersion it already recorded, and evidenceDrift compares a journaled profile against the model running now (deleting the declaration turns the drift into a silent pass and two tests fail). The BLUEPRINT half now has one too: saved-views promotes a composed view by ref, then re-opens it after a deploy moves the fold behind one binding from a rolling week to a calendar month — same hash, same widgets, $750 becomes $1,650, and checkBlueprintPins reports drift on that binding while leaving the unchanged one alone. Its negatives are the load-bearing part: a pre-pin artifact reports compared: 0 with an unpinned finding per binding rather than a clean pass, and an off-catalogue component reaches neither the content store nor the log, asserted on the emitted event stream.
  • Cancellation, Deadlines & Bounded Effects (L3): ADR 0020 — every other K3 enforcement point is ADMISSION CONTROL (it decides whether the next effect runs); K8.4 is the only one that acts inside a call already in flight, which is why it waited on streaming effects and landed only with ADR 0087’s series. A5 predicted it could not be middleware (right — middleware sees the effect once, at the end) and proposed an EffectContext usage-reporter hook (wrong — both wired providers report usage once, at the end, so such a hook could only fire where metering already reads the number). What ships observes the only thing a stream reports continuously, its own output, and names the estimate AS an estimate in the type, the reason and the default. Not L4: no committed bench — the acceptance criterion is a latency claim (<1s) that no bench pins, and provider forwarding is best-effort anyway (A7), so the honest ceiling is L3 via the cross-seam integration.
  • Compiler (L3): Manifest schema is versioned but not conformance-PINNED like the semver ABIs → L3, not L5.
  • Decision Materialization (L3): Proposed as L4, but has no dedicated bench → evidence-derived L3.
  • Document Corpora (L3): ADR 0091 — the package owns exactly one thing: the mapping from a DOCUMENT (the unit a corpus is edited in) to the PASSAGES it is searched in, so a revision supersedes its predecessor instead of duplicating it. Storage is MemoryStore’s, ranking is the backend’s, reproducibility is ctx.decide’s, safety is ADR 0085’s. NOT a seam: there is nothing to plug — an alternative KnowledgeBase would differ only in its manifest layout, and the seam it would need is one layer down. The second consumer (ADR 0083) is db-sentinel, chosen because it is UNLIKE the first: runbook-recall ingests once and asks once, where an append-only corpus and an upsertable one are indistinguishable — which is exactly how the gap this package closes survived ADR 0090.
  • Live Query (L3): Pure read-model — no syscall, no event type (P10). L2 -> L3 once child-orchestration called it: the example polled loadState 400 times at 5ms for a terminal state, which is an interval that is either wasteful or too slow plus a cap that guesses how long a release takes. The supervisor turns spawn intents into child executions and completions into parent resumes off the event loop, so the log is the thing to wait on — subscribe pushes current state immediately, so an execution that already finished still resolves.
  • Tamper-Evident Audit Chain (L3): Stable + tested, incl. a DST crash/recovery integrity property; no version constant (a composable wrapper, not a wire contract). L2 -> L3 once mcp-app-release-approval wired it through storage: { kind: "custom" }: that app’s entire output is a record of who approved a production release, so the K12 authenticator deciding whether an approval is ACCEPTED and verifyChain deciding whether the accepted approval is still the one RECORDED are two questions, and only the second is this plugin’s. The evidence is the negative case — a test rewrites the approver on the durable event and gets ok: false with brokenAt at that sequence — plus the denominator, since a chain verified over a log with no approval in it passes while proving nothing.
  • Typed Resume Payloads (L3): ADR 0077 — the shape counterpart to K12.2’s identity gate (which authenticates WHO may resume, never WHAT they send). KERNEL_ABI unchanged 1.1.0: no syscall added, and ctx.suspend’s type parameter is erased. Opt-in by presence (G2).
  • Child-Workflow Orchestration (L2): Composed from existing primitives — NO new syscall or event type (P10 win; the plan’s CHILD_COMPLETED governed event proved unnecessary). L2; single-child and concurrent fan-out both land on the one suspend/resume cycle, and child failure propagates — awaitChild throws (composing up the tree), awaitChildren settles per-child for the parent to weigh.
  • Classification Path Reach (array crossings) (L2): ADR 0076 — closes a SILENT gap: a sensitive() mark inside an array element collected no path, so it was masked nowhere, encrypted nowhere, externalized nowhere. The @loom/verify classification-reachability rule now covers only what remains unaddressable (record/union/tuple/intersection).
  • Conversation Layer (L2): ADR 0122 (R17). 1P, kernel untouched: KERNEL_ABI, EVENT_SCHEMA and every SPI unchanged; the three additive seams on @loom/agent are loopId, AgentInput.history and AgentInput.promptOrigin. The finding that shaped it: a registered ctx.receive is NOT woken while a step is in flight (ADR 0110 delivers at the owner’s quiescence commit, and a model call is a step), so barge-in cannot be built from the inbox primitives and the signal comes from OUTSIDE the body (a store subscription on the conversation’s inbox stream) — safe because agentLoop$ records every checkpoint it reads. Records are decisions (source: conversation-record), not ctx.emit: an emit is served on a re-run only when the log proves the replay, and an interrupted turn leaves an unsettled step that defeats the proof, so the transcript was appended again on every wake. L2: the suite is the evidence (barge-in kept consistent across a restart with divergence “fail” and across a crash between the interrupting message and its consumption; rotation preserving history and dedupe; a recorded budget verdict that a changed counter cannot re-decide; bounded history reads); no example consumes it yet — the chat-agent loom-examples example named in the ADR is the L3 step. Fix round 1: OWNER-ONLY by default (the runtime’s tenant-scope send would have let any same-tenant principal drive the owner’s agent with the owner’s authority) and an explicit SHARED mode (D10, hardened in fix round 2: host-signed bound envelopes in both modes, an owner-scoped runtime policy required, a service-owned coordinator, verified unguessable turn ids): participants, each turn run as its asker, answers classified and filtered per viewer, summaries built only from unrestricted turns.
  • Conversation SSE Gateway (L2): ADR 0122 D6. The R12 pattern (authenticate returns a principal with an actor; insecure is explicit and refused under strict; loopback default) applied to a streaming edge. Deltas are transient and lossy by design (ADR 0119): a slow client is coalesced, then closed past maxPendingBytes, and recovers from the log; only durable messages are resumable, the in-progress reply is caught up from the hub’s bounded partial. Conversation ids are minted by the server so a client cannot squat or collide. L2: no loom-examples consumer yet.
  • Durable Agent Loop (L2): ADR 0087 — nothing was missing from the kernel; the loop was on the wrong side of it. A step handler receives StepHandlerContext (emit, and nothing else), so 40 model calls were ONE frame, approval had to be hoisted out of the agent, and DECISION_RECORDED was hand-emitted in a shape @loom/learning cannot evaluate. Per-turn durability grows the log with conversation length: the two mitigations already ship (ADR 0054 claim-check for bytes, TieredMemory summarization for turns), and the summarize pass is deliberately NOT wired yet because a lossy nondeterministic summary must be journaled to stay replay-safe. Skill selection is likewise left out until it can go through ctx.decide.
  • Effect Mode Boundary (journaled vs ambient) (L2): ADR 0073 — a boundary CHECK at the execute seam, not a journaling mode: KERNEL_ABI stays 1.1.0, no event type, one replay model. The rejected alternative (ambient = ‘skip the journal inside an execution’) is DE-2’s durability class, a second semantic model inside one execution. Enforced by the entry point, not by context contents, so a transient-looking hand-built context cannot spoof it.
  • Execution-Lifecycle Authorization (L2): L2 (tested; opt-in, byte-identical when unwired). Enforced only when a call carries a principal (trust-boundary model) — a boundary check, not a syscall, so no KERNEL_ABI change; adds EXECUTION_DENIED (EVENT_SCHEMA 1.12.0).
  • Framework-free DOM Renderer (L2): ADR 0127 (U21). 1P plugin, kernel untouched: KERNEL_ABI, EVENT_SCHEMA and every SPI unchanged; depends only on blueprint-runtime and projection-runtime and is not in the @loom/sdk barrel. Written from the Blueprint and the two existing renderers rather than moved from loom-ui’s renderer-lit, whose adapter is bound to loom-ui’s action stamping, i18n and Lit handle contract. No consumer outside its own suite yet (L2, not L3): the candidates are the a2ui-dashboard and db-observability examples.
  • Learning Lifecycle (L2): ADR 0124 (R18). 1P, kernel untouched: KERNEL_ABI, EVENT_SCHEMA and every existing SPI unchanged; the new optional embedding SPI is versioned (EMBEDDING_PROVIDER_SPI_VERSION 1.0.0) with two conformance kits. Canary arms are evaluated per arm (the recorded propensity is the acting policy’s own); there is no rollout controller, no durable ANN index and no version stamp on the effect-ring model paths.
  • MCP Apps ui:// Egress (L2): ADR 0072 D4/D5 — egress only; the host role (iframe, CSP, postMessage) is rejected for this repo (D2), and D8 declines to package one as 3P until a real consumer needs it — it implements no seam, and the part that IS Loom-shaped is pin verification, not sandboxing. No per-flow HTML: the rendered UI is a pure function of (pinned template hash, logged structuredContent), and the pin uses the SAME hashResourceBody the ingress side applies, so a plan-time pin equals what a reader recomputes (ADR 0063 D1). Egress redaction is required-by-configuration, never defaulted.
  • MCP Dynamic Dispatch (L2): ADR 0063 — plugin placement per the ADR 0043 litmus (vendor-specific). Canonical hashing lives in @loom/core (one implementation for drift check + registry change detection). MCP_DISPATCHED/MCP_UI_OBSERVED are userspace domain events (fold skips unknown types — no EVENT_SCHEMA bump). ADR 0072 follow-up (plugins/mcp/src/uiDispatch.test.ts): the ingress UI layer was reachable only from McpClient, because every step seam dispatched through the projection that erases _meta — asserted now through the EFFECT, which is the level a flow actually traverses.
  • MCP Server (L2): ADR 0072 D7 — the prerequisite the MCP Apps work sits on (loom previously spoke MCP as a client only). The UI extension gives the SERVER no in-band audience signal, so audienceOf is deployment-supplied and defaults to “model”: absent an explicit boundary, app-only tools stay unreachable rather than silently open.
  • Model Reasoning as Audit Evidence (L2): ADR 0133. 1P, kernel untouched: KERNEL_ABI and EVENT_SCHEMA unchanged. Gemini continuation verified live (2026-10-20: a turn without its thoughtSignature is the documented 400); the Anthropic mapping and the drop/compaction rules follow the Anthropic documentation and are verified against recorded wire shapes only. L2: the suite is the evidence; the reasoning-audit example lives in the sibling loom-examples repo.
  • Placement Adapters (L2): SPI is L5, but only local/remote/CloudRun adapters exist; K8s/Lambda/GPU are demand-driven (#72) → the adapter set is L2.
  • Planning Depth (L2): ADR 0123 (R16). 1P, kernel untouched: KERNEL_ABI, EVENT_SCHEMA and every SPI unchanged. Reuses ctx.decide / DecideScope (ADR 0119), ctx.parallel scopes (R6), the R10 outcome-as-decision pattern for a failing step, R4’s input validation and JSON schemas, ADR 0085 taint for everything tool- or model-authored that reaches a prompt, and ADR 0111’s durable principal as the goal owner. Waves are barriers on purpose: the schedule is then a pure function of recorded outcomes, which R7’s divergence detector needs. executeGraph (distributed fan-out of executions) is deliberately unchanged. L2: the suites are the evidence (replay with divergence “fail” makes no model call and re-runs nothing; a replan never re-runs or redeclares a completed step; cyclic / unknown-capability / dangling-ref / schema-invalid plans never execute; another principal cannot read, change or detect a goal); no example consumes it yet - the plan-and-replan loom-examples example named in the ADR is the L3 step.
  • Runner-Body Reach Analysis (L2): ADR 0086 — populating FlowSpec.suspends (which had NO producer) proved two suspension-safety checks wrong: the blanket correlationKey error was false (resume routes by executionId, nothing matches on correlationKey) and would have fired on most correct code; the wakeAt check is real only when no TimerStore is wired, since the runtime derives wakeAt from timeout when one is. A check nothing populates is also a check nobody can see is wrong.
  • Saga / Compensation (L2): NO new syscall/event type/fold change (P10 win; the plan’s ctx.compensate syscall + COMPENSATION_* events proved unnecessary — compensators run as replay-guarded ctx.run steps). KERNEL_ABI stays 1.1.0. Verified crash-safe by a DST no-double-compensation property.
  • Sandboxed Reshaping Executor (L2): ADR 0064 — engine choice: quickjs-emscripten (capability-EMPTY VM vs @loom/isolate’s subtractively-scrubbed child process; the isolation CONTRACT follows IsolatedToolSpec). idempotent:true (pure function of script+input); resource kills are typed so replay never re-burns CPU. Written once, audited once — the security test list is the audit’s executable form. ADR 0089 adds an opt-in worker pool (workers, off by default): both engines share ONE evalInFreshRuntime, so the 0064 security list is parameterized over them rather than copied, and the pool contributes what the in-engine guards structurally cannot — a hard kill for a script that never reaches the interrupt handler (measured: 790ms against a 50ms wall clock, one interrupt callback). Pool evidence: plugins/sandbox/src/workerPool.test.ts.
  • Shard Rebalancing (L2): ADR 0075 — builds on 0074. Minimal-movement is asserted exactly (a naive bucket % shardCount moved 80-89% of buckets); reads fall back to the previous plan until a bucket drains.
  • Step Identity & Interpreter Pattern (L2): No kernel changes — a documented contract + compiler lint (tools/compiler loopIdentity.ts, surfaced via @loom/verify’s determinism rule) + crash-matrix tests. Dynamic steps are deliberately outside flowContentHash (K11.1 covers the compiled skeleton only).
  • Storage Scale-Out Decorators (L2): ADR 0074 — capability in packages, optimization in plugins. The decorators now have their OWN committed bench (scaleDecorators.bench.ts), which prices one backend round-trip explicitly and appends 512 envelopes across 512 executions concurrently: 14.8x at the default maxBatch 16 and 51x at 64, tracking the round-trips saved (512 -> 32 -> 8) rather than any backend’s properties. Its second half is the counterweight and the reason these stay inert unless explicitly composed — over a FREE in-memory store there is nothing to save, so coalescing costs 1.49x and sharding 2.27x. The Postgres side is now reproducible too — harness section A2 (coalescedAppend) drives the real decorator over PostgresEventStore.appendBatch and measures 1.5x/2.0x, replacing a published 2.3x/4.3x table that no committed scenario could produce. Still L2, and the missing rung is not measurement: the level bar for L3 is an example or integration exercising it, and the bench itself argues against manufacturing one, since every shipped example runs in-memory where wiring these buys nothing. Promote when a deployment path with a real round-trip exists to point at.
  • Tool Search (L2): ADR 0125 (R22, lexical path). 1P, kernel untouched: KERNEL_ABI, EVENT_SCHEMA and every SPI unchanged. Default OFF (ids unchanged); not worth enabling below ~30 tools. Searching never widens authority: the loaded tool still passes the R4 menu gate, the approval hook and the effect ring. The semantic ranker (R18 embedding SPI) is a follow-up behind ToolRanker.
  • Typed Failure Channels (L2): NO new syscall — TypedFailure is a userspace throw caught at the execute seam (KERNEL_ABI stays 1.1.0, the saga/ctx.compensate precedent). EVENT_SCHEMA 1.17.0: additive STEP_FAILED {channel, payload}. Undeclared channels keep legacy at-least-once retry semantics (G2).
  • Unicode Scoring, Prompt Overrides and Schedule Time Zones (L2): ADR 0126 (R19). 1P, kernel untouched: KERNEL_ABI, EVENT_SCHEMA and every SPI unchanged. ASCII queries tokenize byte-identically and agentLoop$ records its skill selection as a decision, so replay never re-scores. Known limit: an hourly or sub-hourly cron in a DST zone loses the repeated hour’s second pass; schedule$ warns once at definition time.
  • Untrusted-Content Integrity Axis (L2): ADR 0085 — a value wrapper, NOT a fourth schema marker: confidentiality is a property of a field (static, forever), integrity is a property of a value (acquired at runtime from a specific origin), and a marker that guesses reads as coverage. The check is unconditional because a gate defaulting to off protects exactly the apps that never think about it; the opt-in sits on the producer, where tainting changes the output shape. The REFUSAL is structural; the FENCE is a label a model can be talked across — do not sell the second as the first. AMENDED (R2, 2026-08-18): this entry previously said the guard ran inside every createLlmEffect — true, and not load-bearing, because createLlmEffect had NO production consumer while AgentRunner and ReasoningRuntime called provider.complete directly. The agent path additionally ERASED the evidence (JSON.stringify flattened the envelope, so isTainted was false downstream and no later guard could recover the origin). Both paths now guard; the erasure is fixed by re-tainting the serialized body. EXTENDED (R5, ADR 0120): the same positional rule covers content PARTS — a tainted tool result stays a fenced tool_result in a user message, a tainted tool output is never interpreted as media (it stays fenced text) and mediaResult is honoured only for a tool declared returnsMedia, media marked untrusted is bracketed by fence tags (by each provider mapper) and refused outside user or when it names a stored ref, a tainted value anywhere in a tool_use is refused, and guardRequest additionally enforces part structure, the inline-media byte cap and tool_result pairing; providers declare modalities and an undeclared one is refused before any network call.
  • GPU Worker (L0): Concept only — there is NO gpu-worker package. Demand-driven (#72). A hand-assigned ‘L1’ would be a badge that lies; evidence-derived it is L0.

Source: docs/spec/maturity.md in the loom repository.