@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
@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
AnEngine 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
AMemory 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
EngineErrorcarries akindfromEngineFailureKind:auth,billing,quota,rate-limit,model-unavailable,missing-cli,invalid-config,transient,abortedand the rest of the union.EngineIncompleteResultErroris a failed turn that still produced measured evidence.GraphValidationError: an invalid definition, description or plan.GraphExecutionErrorwith aGraphExecutionErrorCode: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.StorageErrorwith aStorageErrorCode: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, withpath.MemoryErrorCodeon 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
- Safe node attempts: the engine contract in a running file.
- Plugins and tools: the adapters that implement these contracts.
- Events and artifacts: the storage ports and their conformance kits.