Skip to main content
@obversa/api holds the contracts the runtime and the plugins share: what an engine is, what a memory adapter is, how a graph plan is resolved, and the shape of every stored event, artifact, callback and proof record. Import it to write an engine or memory adapter, or to validate records a host reads. Command execution belongs to Core, memory helpers to Runtime.

Install

Included in @obversa/obversa.

Quickstart

Validate an engine’s result before you read its parts:
examples/safe-node-attempt.ts (excerpt)
validateAgentResult checks an AgentResult against the contract and returns it; finalResultPart and finalResultText read the one final part. The whole file is on Safe node attempts.

Implement an engine

An Engine has a name and a run method: one AgentRequest, one event sink, one abort signal, one AgentResult. It may also have admit, which the runtime calls before a run when the plan asks for engine checks; admit receives the request without its prompt and returns an EngineSelectionRecord, the identity the engine will run under (adapter, adapter version, provider, model family, model, executable, capabilities). An engine that would run as something else refuses. An engine without admit is unsupported for the check, and the plan says whether that blocks the run. A command-line adapter is admitted by path and version, not by a hash of the executable, and doesn’t control grandchildren it can’t see. An API-key adapter is admitted locally with no executable. A live check is an ordinary run with purpose: 'preflight', no tools, no workspace and a leaf request; it proves the seat answers and does no work. runEngineConformance checks an engine through its real process or provider boundary. A feature the engine lacks goes in unsupported with its reason; the report lists it and doesn’t count it as a failure. One case is about your setup: the kit runs a read step once with the engine’s clean option off and once with it on. Clean is the default, so the fixture opens the engine with clean: false for the workspace-read scenario. The fixture’s observe() reports ownSetup from the arguments or options the engine really passed. The case checks the run with clean: false passed no clean-mode switches, the clean run passed them, and both stayed read-only. An engine with no clean mode declares clean-mode in unsupported. So does an engine that loads none of your setup and always runs clean: it has no clean option, so the kit has no second run to compare. modelIdentity(model) reads the provider and model family from a model string: provider/model supplies both, a bare model only its family (the lowercase part before the first hyphen). Whitespace, a second slash, an empty provider or model, or an empty or unknown family is refused with an EngineError of kind invalid-config. Every harness that runs other providers’ models derives its identity through it. classifyEngineFailure turns a failure message into an EngineFailureKind, and LANE_DEAD_FAILURES names the kinds a fallback treats as lasting.

Implement a memory adapter

A Memory adapter has one scope and one execute(command) method. MEMORY_ROOT is /memories, and every path starts there. MemoryCommand, MemoryResult, MemoryLimits and MemoryErrorCode describe the requests, results, limits and errors; the commands and codes are on Memory port. runMemoryConformance from @obversa/api/testing checks an adapter without a network service.

Resolve a plan

resolveGraphPlan(description, resolution) binds a graph description to a host’s admission record and freezes what the run may use:
examples/custom-graph.ts (excerpt)
compileGraphDefinition validates a definition’s data, and validateGraphDescription a description that didn’t come from compileGraph. Plan admission is the guide.

Validate stored records

Every stored shape has a validator that returns it frozen or throws: validateRunDefinition, validateRunStartRecord, validateRunStorageRecord, validateRunStoragePolicy, validateDomainEventEnvelope, validateDomainEventBatch, validateNewDomainEvent, validateDomainEventId, validateEventStreamRef, validateStreamRevision, validateStorageId, validateArtifactReference, validateNewArtifact, validateArtifactScope, validateCallbackRequest, validateCallbackResponse, validateCallbackEvent, validateAcceptedResultRecord, validateApprovalRecord, validateApprovalSubject, validateActionDecision, validateResolvedPlan, validateIncompleteResultEvidence. The example reads an artifact reference back out of an event before trusting it:
examples/durable-storage.ts (excerpt)
canonicalJson, digestJson and cloneFrozenJson are the JSON helpers the contract uses; findKnownSecretInEvents finds a configured secret in an event batch before it’s written.

Bind an approval

createCallbackGate(definition) turns a gate definition into a request with a content identity, and createApprovalCallbackGate(definition, subject) puts the subject digest in first, so the question is about exact bytes:
examples/proof-bound-approval.ts (excerpt)
approvalSubjectDigest, snapshotApprovalSubject, assertApprovalPermissionsAdmitted and acceptedResultMatches are the pieces under Proof-bound acceptance and approval. callbackRequestDigest gives a definition’s digest without creating a request.

Options

The contract’s option types are on the pages that use them: AgentRequest on Safe node attempts, PlanResolution on Plan admission, RunStoragePolicy on Events and artifacts. This package adds no options of its own.

Errors

  • EngineError carries a kind from EngineFailureKind: auth, billing, quota, rate-limit, model-unavailable, missing-cli, invalid-config, transient, aborted and the rest of the union. EngineIncompleteResultError is a failed turn that still produced measured evidence.
  • GraphValidationError: an invalid definition, description or plan.
  • GraphExecutionError with a GraphExecutionErrorCode: ABORTED, DUPLICATE_POSITION, EMPTY_DECISION, ENGINE_IDENTITY_UNRESOLVED, INVALID_EVENT, INVALID_PREFLIGHT_CONFIG, MISSING_ENGINE_BINDING, MISSING_MEMORY, MISSING_NODE_BINDING, PROTOCOL, RESUME_EVENT_MISMATCH, STORED_GRAPH_MISMATCH.
  • StorageError with a StorageErrorCode: INVALID_STORED_VALUE, UNSUPPORTED_ENVELOPE_VERSION, REVISION_CONFLICT, DUPLICATE_EVENT_ID, CORRUPT_EVENT_STREAM, ARTIFACT_NOT_FOUND, ARTIFACT_NOT_ADMITTED, ARTIFACT_INTEGRITY, STORAGE_LIMIT_EXCEEDED, SENSITIVE_CONTENT, KNOWN_SECRET, UNSAFE_STORAGE_PATH.
  • ApprovalSubjectError: a permission outside the stored plan (SUBJECT_MISMATCH).
  • JsonValueError: a value that isn’t JSON, with path.
  • MemoryErrorCode on a memory result: the fifteen codes on Memory port.

API

Engines. Engine, AgentRequest, AgentResult, AgentResultPart, EngineSelectionRecord, EngineFailureKind, isEngine, engineSelection, assistantResult, reportedUsage, finalResultPart, finalResultText, requireFinalResultText, validateAgentResult, modelIdentity, classifyEngineFailure, LANE_DEAD_FAILURES, assertReadAccess, TeamSeat, SUBAGENT_TOOLS, CLAUDE_SUBAGENT_TOOLS. Memory. Memory, MemoryCommand, MemoryResult, MemoryLimits, MemoryError, MemoryErrorCode, MEMORY_ROOT. Graphs and plans. compileGraphDefinition, resolveGraphPlan, validateGraphDescription, validateResolvedPlan, GraphDefinition, GraphType, GraphDescription, PlanResolution. Callbacks and proof. createCallbackGate, createApprovalCallbackGate, callbackRequestDigest, approvalSubjectDigest, snapshotApprovalSubject, assertApprovalPermissionsAdmitted, acceptedResultMatches. Stored records. The validate* functions above, canonicalJson, digestJson, cloneFrozenJson, findKnownSecretInEvents. Subpaths. @obversa/api/testing: runEngineConformance, runEngineAdmissionConformance, runMemoryConformance, runEventStoreConformance, runArtifactStoreConformance, runWorkspaceProviderConformance and their assert* forms. @obversa/api/run-definition-support: runDefinitionSupport. @obversa/api/accepted-result-support: acceptedResultSupport. @obversa/api/approval-support: bindingFromSubject, hasExactFields, isObject.

Next steps