Skip to content

Semantic capture contract v1 ​

backend/services/semantic/contracts.py owns the strict semantic schema. It imports neither Torch nor user models. The schema version is independent of graph layout and portable snapshot envelope versions. Live canonical producers and consumers use this schema; legacy captures retain compatibility paths with explicit limitations.

Generate/check the shared extension and browser TypeScript with npm run semantic:generate --prefix extension and npm run semantic:check --prefix extension. Set PYTHON to the project's backend interpreter when needed. The same exporter generates validation contracts without changing their schema. Generated JSON schema checks structural types; Python additionally checks relationships, event ordering and resource budgets.

Resolved execution plans ​

Browser saved profiles and explicit MCP execution use ResolvedExecutionPlan. Isolated preflight records saved source/configuration/native-file digests, selected interpreter and package facts, exact execution and optional diagnostic recipe, timeout and semantic capture budgets. Factories, checkpoints and input fixtures must have the correct source/native identities. Selected profiles must match the saved profile, target and applicability; explicit recipes record configuration as absent or overridden. Automatic input guesses use CapturePreparation instead.

CapturePreparation is the preceding automatic-input stage. It preserves the exact saved source/environment, configuration status (absent or no_match), bounded original request and limits without claiming a chosen input recipe. The static host resolver rejects dirty source/configuration, malformed configuration and matching model/script profiles that require selection. It shares the plan's source identity, applicability hashing and saved-file/environment rechecks. Browser/MCP automatic requests, including trace-on-save, retain canonical evidence against this preparation. Fake-tensor and non-model legacy paths do not claim this prepared canonical format.

Python owns the canonical applicability fingerprint. It excludes per-attempt IDs, source occurrence IDs and editor document versions, while retaining saved bytes, target, environment, recipe and limits. This is an applicability key, not proof of identical runtime state: actual inputs, weights and RNG state have their separate existing measured identities. Source scope remains explicitly partial.

The host transports the plan in a private 16 MiB-bounded temporary file, with mode 0600 inside a 0700 directory on POSIX and a restricted ACL on Windows. It cleans up on every exit path. The worker verifies saved bytes and environment before importing workspace code; host and worker reconcile again afterward. Stale captures remain available without validating current settings. The MCP setup query pages plan files. Workers enforce independent canonical operation, invocation, value and byte budgets. Canonical runs bypass the legacy operation/reuse stream and folded display graph. A separate diagnostic sidecar retains bounded operation/module/output evidence for numerical and profiling consumers. Library/fake callers and older captures retain their compatibility path; they do not claim canonical semantics.

Preserved observations ​

  • Operations are generic target/schema invocations, never an opcode admission list. Ordered positional and keyword argument trees preserve repeated references, containers, literal types, symbolic expressions and opaque boundaries.

  • Integer literals are canonical decimal strings so JavaScript cannot round Python integers. Float tags preserve the float/int distinction and explicitly encode nan, inf, -inf and negative zero (-0). JavaScript JSON retention would otherwise turn numeric negative zero into positive zero. Symbolic expressions are descriptions, never code to evaluate. Unknown objects expose only a type name and omission reason.

  • Argument and output paths address the serialized JSON tree. For example, the second tensor in cat([a, b]) is ['args', 0, 'items', 1]. Dictionary entries preserve order and typed keys; a value path includes entries, the entry index, and value. Rendering can give these paths friendly labels without merging them.

  • Tensor facts reuse validation's object/storage/version identities and add producer output/effect paths, strides, storage offsets and parameter/buffer references. version is PyTorch's observed counter (nullable for inference tensors), while logicalVersion numbers this object's successive retained observations. A schema-declared write advances a logical version even if dispatch does not advance PyTorch's counter. previousVersion links the preceding value. They contain no tensor payload. Shape equality never establishes aliasing.

  • Each operation has explicit effects for writable operands and already observed shared-storage aliases. Effects cite an exact argument path, prior value and new value or opaque limitation. Repeated operands keep distinct paths. A producer's kind selects the operation's output tree or its effects array; effect paths such as [0, 'after'] never invent a returned tensor for a None result. These are conservative schema-declared write boundaries, not measurements of which tensor elements changed. A failed writable kernel remains partial even when the post-failure metadata is available.

  • Definitions, runtime module instances, and particular calls have separate IDs. One module can have multiple registered paths and multiple invocations. Entry/exit sequence numbers describe containment, independently of computational dataflow.

  • Optional Value.logicalBytes records actual numel() * element_size(), including scalar, empty and strided/view values. It is not allocated or peak memory. Invocation.parameterCount records unique recursive registered parameter objects at call entry, excluding buffers and counting shared registration only once per invocation. Missing legacy facts stay unknown. Optional parameterCountUnavailable distinguishes unsupported metadata and exhaustion of the separate 100,000-item per-capture metadata-read allowance; neither supplies a partial count or changes operation coverage. Browser and MCP summaries use these same facts, with no dtype-to-byte inference or first-output selection.

  • Source bindings cite an immutable snapshot and exact file digest plus source/token anchors. Exact, candidate and ambiguous correspondence remain distinct. A binding alone does not grant edit capability. Current captures require saved source; buffer overlays have not been implemented.

  • Fake tensors. The recorder observes a FakeTensor (PyTorch's shape-only tensor) exactly like a real tensor: a Value keeps its identity, shape, dtype, device, stride, storage offset, logicalBytes, producer and state references, and the argument trees, operations, invocations and parameter counts are the same. A FakeTensor's .device is a dispatched prim.device call; it is not recorded, as the legacy fold already filtered it. An unsupported FakeTensor is opaque under the type the same program has with real tensors (torch.Tensor or torch.nn.Parameter). Values never exist, so a capture that observed a FakeTensor value or registered parameter/buffer sets valuesUnavailable: "fake_tensor", and the fingerprints of fake tensors are null (stateDigest, and inputDigest for fake inputs). Fingerprinting refuses fake and meta tensors before reading any memory (execution_setup.TensorValuesUnavailable); state_identity reports unavailable: "fake_tensor" or "meta_tensor". Real captures omit the field rather than writing null, so they serialize exactly as before. One known difference: creating nn.Parameter(...) inside a forward dispatches extra aten.detach calls on a FakeTensor. Such dynamic parameters already leave the capture partial.

Coverage and limits ​

A computational connection must come from a recorded value reference at a specific argument path. Producer outputs must identify that value and occur before its use. No layout edges belong in this contract.

Coverage describes only the executed path. observed, failed, not_observed, capture_limited, static_only, unresolved, and state are separate states. Coverage cannot label a module unobserved when its calls are present. Omitted events require a reason. Unsupported arguments and exhausted budgets prevent a capture from claiming completeness; a failed forward may retain a useful partial prefix. Operations, invocations, values and serialized bytes have independent limits.

Attempt state and retention ​

RunAttempt records the latest execution outcome separately from capture identity. Its request digest and origin exist even when setup refuses. A preparation can establish source identity while the plan digest remains null; an automatic guess or invalid configuration never receives an invented plan. The worker reports its resolved plan before launch so a timeout can retain that setup without a capture. CaptureState separately names the last complete, last partial and displayed captures. The host ledger uses these records for manual, automatic and MCP runs. A timeout can terminate an attempt without producing a new capture; a projection failure can retain a new capture while displaying an earlier one. Legacy evidence is explicitly partial, even after a successful forward, because its completeness is not established. It cannot fill the complete semantic-capture slot. Captures are immutable JSON copies, bounded to 16 MiB each; only complete/partial/displayed slots remain retained. Oversize captures retain their identity with an explicit retention-limit reason. The latest resolved plan or preparation has a separate 16 MiB bound. These are host-session records, not disk persistence. Planned and prepared automatic captures populate the complete/partial slots after host schema, execution-identity, budget and basic completeness checks. Python remains the owner of full relational validation. Invalid or duplicate canonical payloads cannot replace complete evidence; their legacy result remains explicitly partial with a host-private rejection reason.

The browser's bounded validation.evidence summary carries attempt origin/outcome, source/plan identities, retained slot IDs and displayed-capture attribution. Its optional resolved setup labels contain only profile name, device and mode. Full recipes and interpreter paths remain host-owned. Reconnect rebuilds attribution from the current ledger, and a late capture can update it without replacing a newer refused attempt. Legacy and incomplete canonical captures are labelled incomplete in the result panel. Capture completeness does not certify the legacy canvas projection.

Captured observations are not another execution-input language. Saved-input materialization continues to use services.validation.contracts.Binding; explicit setup/fixture/checkpoint requests continue through the existing execution machinery.

Recorder and identity ​

services/semantic/identity.py is shared by the capture adapters. It uses weak references and monotonically assigned IDs; producer identity does not require keeping activations alive. Releasing an object clears its lookup entry, while already serialized metadata remains. Input-boundary seeds use those same recorder identities. The canonical recorder and legacy compatibility adapter share this identity implementation.

services/semantic/tensors.py owns the common weak tensor object/storage/version observations used by the validation recorder. That legacy adapter still rejects writes it cannot express using PyTorch counters. The canonical recorder extends the snapshot key with a declared-write epoch and keeps weak per-storage alias indices; no alias inventory keeps activations alive. The semantic argument encoder preserves positional/keyword order, repeated references, typed literals, symbolic descriptions and nested output paths. Its bounded built-in traversal records an opaque subtree for unsupported objects, cycles/depth, large containers and exhausted value budgets; it never evaluates an expression or serializes an object's repr. Workers using resolved plans publish diagnostics.semantic_capture, validated against the canonical contract, alongside the legacy graph. semantic_capture_error is an explicit bounded reason when no canonical payload can be retained. The worker's five-key/version-1 envelope is unchanged. Prepared automatic workers publish the same contract with planDigest=null, a preparationDigest and selected-candidate provenance/index/total attempts. Planned captures have a plan digest and null automatic fields. Exactly one setup identity is required. Only the selected call is retained, and search's reuse of model state is disclosed. Actual input and pre-call state digests remain separate from the preparation's applicability key. Imported source outside the saved snapshot discards canonical attribution with source_mismatch; changes after importing original bytes retain their original capture with a stale current flag. The host rechecks saved setup after worker exit and after asynchronous graph projection. Source/import/configuration/environment changes keep retained captures inspectable but cannot certify the current graph.

The existing dispatcher and module-call hooks feed services/semantic/recorder.py; there is no second dispatch observer. Definitions, registered aliases, actual module instances and calls are separate. Unregistered dynamic modules have an empty paths list, not an invented source path. Parameter/buffer references are observed from the entry inventory. Dynamic state without that identity remains incomplete. Custom operations keep their target and dispatcher schema as atomic operations. Python statements and kernel internals are outside this representation.

Canonical operations and failed invocations share the bounded forward diagnostics. Proven operator shape/dtype constraints survive in the capture; arbitrary exception prose is never parsed for dimensions or copied into diagnostic facts. Exception causality distinguishes a propagated operator failure from a later assertion or explicitly suppressed error. Identical safe facts can share a diagnostic record; operation/invocation identities still distinguish the events. Diagnostic byte/item exhaustion leaves an explicitly incomplete capture and a reserved generic failure link. The recorder does not retain extra exception/traceback objects for this mapping.

The function boundary completes tensor metadata after PyTorch updates autograd and mutation versions. Output/effect ports preserve producer paths and earlier values remain immutable. Non-returned writable values have explicit effect producers. Some custom dispatch kernels do not advance PyTorch's version counter as an ordinary forward would; the capture records that observed counter without inventing a change, and discloses its separate schema-write logical versions in normalization metadata. Potentially partial writes from failed kernels remain incomplete. Escaped mutable storage and unsupported tensor/argument types also prevent completeness. Instrumentation errors do not replace a model's result. No-capture byte errors can occur when even the immutable source/envelope exceeds the requested budget. Conservative reservations bound incremental retention before the final relational/byte validation. Tensor producer paths are charged at their encoded size together with the output or mutation effect that owns them. Event reservations cover only bounded terminal fields and an opaque fallback; unused blanket padding must not discard a graph that fits. A byte stop keeps that reason instead of also claiming the invocation count was exhausted.

Source correspondence ​

Source cards join exact saved-source captures through SourceCorrespondence: registered paths identify module instances and retained invocations, shared aliases remain explicit, and parameter/buffer values are not described as module calls. Missing calls, capture limits, source-only declarations and unresolved expressions have separate labels. A single shape is summarized only when every retained call has the same direct tensor shape/dtype; multiple outputs and varying calls keep their full trees without a misleading first-tensor summary. Failed calls keep their measured input and no invented output. Joined observations bypass the legacy shape rules and runtime fallback, including an explicit unknown output.

The shared validation loop passes its host attempt ID into plan resolution. That exact ID is used by the plan and canonical capture, including manual, MCP and save-driven attempts. Direct worker callers without a host attempt still receive a fresh ID. The applicability digest continues to exclude per-attempt identity.

Contract tests cover repeated operands, ordered nested outputs, large integers, symbolic values, invocation sharing, aliases, mutation versions, source revisions, partial failures, contradictory coverage, budgets and generated-file parity.

Reversible projection v1 ​

services/semantic/projection.py derives CaptureProjection from a validated capture without importing Torch. Every operation and invocation has its own node; the projection refers back to their captured IDs rather than copying or lowering their meaning again. Ports address exact serialized argument/output/effect paths. Literal, symbolic, opaque and empty-container ports remain inspectable without fake tensor connections. Multiple root input names for the same value remain explicit aliases; Whisper-style distinct input tensors keep separate ports.

Connections exist only for captured tensor argument uses, effect predecessors and returned values. Their source is the actual producer output/effect or an observed input/state/external value. An unused model input is never connected by layout rank. An identity module retains its invocation interfaces without an invented dispatched operation or a claim that returning the same tensor creates a new producer.

Collapse retains every original node, port and connection. Only visibility and display endpoints change. A boundary port cites the exact member port it replaces, including the original destination argument position; internal connections keep their original endpoints while hidden. Expanding regenerates the identical detailed projection. Hidden-operation counts count actual retained records. State/external source nodes are placed at the common scope of their uses so private state can hide inside a collapsed region and shared state remains shared. This placement is a view choice, not a change in parameter or module ownership.

The Python and generated TypeScript projection contracts validate ownership, visibility, hidden counts, value identity, port direction and collapsed endpoints. The producer, live document projection, canvas ports, MCP topology and portable viewer use this contract. Legacy captures retain their original semantic limitations.

The GraphDocument handoff preserves declared ordered input/output port IDs (including empty and unconnected ports) and exact edge handles. Legacy nodes without declarations derive their port IDs from actual connections, falling back to the old x/y defaults only when necessary. Python publishes semantic parent ownership directly, and browser/MCP readers prefer it over legacy canvas copies. The legacy recorder does not gain argument semantics it never captured.

Live captured documents ​

services/semantic/document.py projects canonical captures directly into the existing GraphDocument envelope, bypassing legacy source/trace edge splicing, rank-based edges and destructive repeat folding. Both single-file and multi-file projection use the same producer. Invalid or foreign captures refuse; they never fall back to legacy wiring. A valid partial/identity capture is usable even when the old visual recorder produced no nodes.

Each rendered record carries CaptureReference (capture/source, kind, semantic ID), ordered DisplayPort records (exact argument paths, value identity, shape, dtype, device), semantic parent ownership and actual descendant operation counts. Occurrences are scoped to their capture, so reused event ordinals in a later run cannot silently become the same selected card. Repeated/shared calls have separate records and readable occurrence labels. Each edge keeps its original ProjectionEdge.

The host retains the full CaptureSnapshot for subsequent inspection. The document has a compact capture summary and source-correspondence table, not a second copy of the environment/setup/argument payload. Its independent 24 MiB decoded budget leaves room for spans and message metadata within the transport's 32 MiB decoded ceiling. Large messages use lossless gzip JSON while the broker's wire limit remains 8 MiB. Overflow refuses with a display-budget message while retained evidence remains available. No records or ports are removed to fit.

The browser keeps original ports/edges intact. Collapsed interfaces use ephemeral boundary:<memberPortId> identities and one display connection per original use. Repeated operands remain distinct. Port pins and both initial/dragged edge anchors share deterministic geometry; card height accounts for the port count. Literal and empty-container ports have no invented tensor edges. Root boundaries display each typed port, including scalar [] and differently typed inputs, rather than a first tensor summary. Expanded calls show their actual retained descendants; no repeat representative replaces another captured occurrence.

Document validation checks references, typed-port consistency and recorded shape summaries without running inference or constructing Torch layers. Its results describe retained evidence, not a new execution or certification of other inputs. Failed calls keep their own error evidence; the propagated terminal failure maps to the canonical operation or root call. Static opening remains unchanged and never imports user code. Source spans on uniquely registered calls permit navigation; they do not grant an edit capability.

Retained capture inspection ​

The host CaptureInspector reads only the ledger’s validated immutable canonical records. MCP list_captures and inspect_capture expose the independent slots and page the exact JSON tree at each container, preserving argument types/order, repeated references, state aliases/versions, effects and coverage. Container entries carry child counts; leaves keep exact scalars. Pages stay within 64 KiB and never replace a large operation tree with an omitted-record placeholder. Queries require exact capture/source identities and model scope; stale source does not destroy retained evidence, while live focus still checks current revisions. The browser uses this same inspector through private correlated request/reply messages and a temporary Captured details dialog. Ordered argument/output records link to their tensor facts; whole-capture browsing exposes state, coverage and diagnostics.

MCP runs retain the same validated payload under their independent existing 16 MiB-per-run/64 MiB-per-window allowance, 32-run count and 30-minute/session lifetime. Live-slot eviction or model retargeting does not erase an owned retained run. inspect_run uses this reader for its canonical capture, operations and modules sections; legacy runs disclose unavailable semantics. Rejected capture identity cannot be replaced with legacy operation/coverage claims. Run graph snapshots retain their projected capture identity, module instance IDs and every shared registration path. Capture and graph retention failures stay distinct. A second worker callback cannot replace the first captured run evidence, even while projection is pending.

Failure inspection traverses recorded value producers (including nested argument trees and mutation-effect predecessors) from operations sharing the root diagnostic. These are explicit diagnostic candidates: equal safe diagnostic facts can describe multiple caught failures, and do not establish exception causality. No operation is invented for an assertion before dispatcher entry. State/argument-aware comparison shares the canonical host/browser engine across MCP and the portable viewer.

Portable capture v1 ​

PortableCapture versions the detached payload independently from runtime and graph file versions. services/semantic/portable.py validates and copies canonical evidence without importing Torch or workspace code. Inputs, outputs, operations, invocations, values, effects, aliases, module/definition identities, diagnostics and coverage stay exact. Workspace/model/session identities are replaced by portable, editor versions are cleared, and file references (including source anchors) use deterministic aliases. The conventional configuration name remains tensorviz.toml. A digest identifies the original source snapshot without publishing its paths; detached source cannot certify an execution plan from the live workspace.

Literal privacy is lossless-or-refuse: absolute/path-like literals and literals under recognized private/internal argument names refuse export with a fixed message. Nested containers are checked; tensor references and None remain valid under those names because they contain no payload. No operand is removed or replaced. Ordinary model names and literals are computational evidence, so this is not a general secret detector or an assurance that arbitrary user labels contain no personal information. Source bodies, tensor payloads and environment paths are not capture fields.

Shared fixtures and executable corpus checks cover the producer and contract. Version 4 files, live export, offline validation/inspection and canonical comparison consume this payload. Uncertain correspondence remains explicit.

packages/contracts/src/captureReader.ts owns the nonexecuting structural reader shared by host/MCP and the browser's checked generated copy. The offline client accepts an already-validated capture and provides the same bounded pages to Captured details; it has no network or source-edit capability. File import and the viewer use it. UTF-8 byte limits, exact argument reconstruction, foreign-identity refusal and prototype-safe own-property traversal apply identically in both environments.

The version 4 graph-file importer validates the detached payload with precompiled Python-generated JSON-schema validators and explicit offline relationships/privacy checks. Shared Python/browser acceptance cases cover producer/alias/version ordering, ownership, boundaries, coverage, limits and non-returned mutation. Its document gate checks every rendered event, exact argument/effect/result use, producer and value fact against the payload. File layout can reorder display records but cannot omit events or rewire them. The offline viewer uses the shared reader and discloses partial and legacy evidence. The integrated canonical comparison preserves those limitations.

Live export uses the same retained record ownership checks as inspection. The host rechecks displayed target/capture, eviction and connection epoch after Python detaches the payload. Private request/reply transport never broadcasts the capture or retains it in reconnect snapshots. One export can prepare at a time; cancellation aborts the request. Authenticated preparation bounds raw input to 16 MiB and detached output to 6 MiB, with exact computational equality checks in the host and full offline validation in the browser. The browser rechecks current projection and capture before download; layout changes during preparation are preserved. No source is read or model executed.

Verified authored source locations ​

Canonical definitions, calls and operations can cite exact saved-source bindings. The existing code verifier supplies bounded, digest-checked source before model import; PEP 657 instruction ranges are joined to one AST expression in the same verified code scope. Definition bindings identify a known forward implementation. Coordinates are one-based lines and zero-based UTF-16 columns. No source bodies or live frames are retained in the capture. Unknown ranges/external helpers remain unmapped.

A binding is location evidence, not an edit capability: one expression may produce several operations or repeated calls. Binding metadata has its own bounded share of the overall capture budget. Supported constructor, argument and direct scalar configuration bindings have explicit edit capabilities and shared scope; ambiguous or unsupported recipes refuse.

Stored dictionary outputs ​

Dictionary subclasses, including Transformers ModelOutput objects, expose their stored entries as dictionary argument trees. Built-in dict/OrderedDict methods read the storage directly, retaining OrderedDict order without calling overridden items, length, iteration, lookup or repr methods. Custom object attributes and computed mapping behavior are not reconstructed; the capture's normalizations record this distinction. Existing key, item, depth and tensor budgets still apply.

Tensor outputs now retain their identities and measured shapes instead of becoming opaque solely because the container is a dictionary subclass. Unsupported nested objects remain explicit: for example, T5's EncoderDecoderCache still yields partial coverage while its encoder and decoder tensor outputs remain visible.