@alma-harness/single-call
Governed one-dispatch model calls. The text/JSON runner is tool-free; the structured step runner preserves native tool calls. Use matching core, execution, providers, memory and PostgreSQL packages. Node >=22. This engine is separate from the legacy conversation and batch runners.
createSingleCallRunner(config) owns policy, versioned prices, monetary caps,
complete cache controls, output schema/validator and technical limits. run
accepts a verified scope/session/operation key/input revision/intent, an optional
AbortSignal and a lazy loadInput(): Promise<Msg[]>. The host resolves identity
and immutable input authorization; never forward browser-supplied scope/revision
without that resolution. Only text user/assistant messages are supported.
Supply ExecutionStore, ExecutionResultStore, UsageInbox, GovernedCostSettlementStore, a SpendStore.peek reading the same counters, and an awaited AuditLog. Use InMemoryGovernedCostSettlementStore.peek with its own volatile settlement store; PostgresSpendStore and PostgresCostSettlementStore share the durable tables. Only governed settlement writes financial rows. AuditLog.cost and SpendStore.add are never invoked by this engine. Routing/access carry operation/attempt/call IDs.
Configuration includes policyVersion, priceVersion, outputContractVersion, prices,
caps, consumers, system, maxTokens, maxInputChars, maxOutputChars, deadlineMs,
resultRetentionMs, clients and onBackgroundError. Optional cache overrides the
policy cache, including false; omitted uses the policy choice. Optional output
is {name, schema, validate} for native JSON; validate must synchronously return
true. Configuration data is snapshotted; host version identifiers must describe
immutable policy/output contracts. Clients/policy/validator are trusted capabilities.
Use the providers package's explicit SingleDispatch constructors: default clients
with automatic retries are rejected. No fallback, repair, tools or user streaming.
Results are reserved before loading context. Duplicate keys read status or the original authorized envelope, never load input or redispatch. Changed session, intent, revision or policy/output/price version conflicts. Monetary warnings preserve results. Explicit block caps retain preflight/post-call blocking; known cost is always settled before a successful output is suppressed.
read(scope,key) returns null or a view: in_progress, reconciliation_required,
not_dispatched or completed. Completed includes the current authorized envelope,
original financial receipt and historical journal metadata. Erased/expired output
is unavailable even when the historical completion was successful. Persistence
failure rejects; reread/recover resolves what committed without a provider retry.
A not_dispatched view's record.notDispatchedCause says why the claimed call
was never sent to the provider, when that was observed: only invalid (refused
as configured or loaded) supports suppressing an unchanged retry; input is the host loader's
own failure; aborted, audit and store usually pass; absent or expired
means unknown. Retry only under a new key, with a bounded policy. The structured
runner's root-only view has no record and no cause (spec: not-dispatched-cause).
recover(scope,key,{leaseMs}) acquires an eligible accounting-only fence, uses the
original policy/prices/day and completes available or unavailable output. A policy
refusal during unfinished recovery leaves completion pending rather than replacing
a previously stored envelope. It never
fetches input. Call reconcileExpired(scope,{limit?}) from the host scheduler to
fence overdue prepared/dispatching executions. Unknown/unpriced evidence requires
operator reconciliation; late inbox evidence does not automatically rewrite it.
reconcileUsage(scope,{limit?,after?}) acknowledges only exact observations whose
completed accounting or not-dispatched proof is durable. Page size defaults to50,
maximum100. It returns scanned/acknowledged counts and the last inspected cursor.
Restart each pass from the beginning to see late commits. Unknown/conflicting
observations remain pending. Financial projections have independent consumers. The optional third argument { skip(operationKey) } leaves rows pending. Runtime
maintenance uses it to keep a governed call's usage on the step path, which also
checks root accounting (spec: runtime-maintenance).
The public run promise is bounded by deadline/abort, including hanging dependencies. A dispatched stream remains drained for late evidence; background errors reach the required metadata-only onBackgroundError callback. Process death or a permanently hung custom adapter cannot guarantee final usage arrival. Hosts monitor unresolved records and schedule recovery; this package starts no persistent worker.
Migrate journal, results, inbox, governed costs and audit before deploying the matching readers/writers. Result retention must allow execution/recovery and its erasure surface must be registered by the host. Completion does not mean a business mutation or portal/WhatsApp delivery happened: those have independent identities. Erasure can still race egress after the final reservation check; stored content cannot be resurrected. See spec.
Optional temperature is a host configuration snapshot, persisted with execution
controls before dispatch. Provider validation applies before claim. Replay and
accounting recovery retain the original value; callers cannot override it. Bump
policyVersion when changing this immutable configuration. See the providers
package's deliberately bounded support matrix (spec: explicit-temperature-controls).
Governed model steps
createGovernedStepRunner(config) is the official buffered primitive for a
one-call structured response and the primitive adopted by conversations. It accepts a trusted, already planned OperationCallInput and root fence,
plus a lazy loadRequest and routing rationale. Supply the same execution/result/
inbox/settlement/spend/audit capabilities, OperationTreeStore, OperationAccountingStore,
single-dispatch clients and runTimeoutMs/maxRequestChars/maxOutputChars (optional
maxOutputChars). The host
builds registry-derived tools and enforces authorization before handing it over.
Input prices, policy, controls, IDs and retention are immutable original snapshots.
The root slot is reserved before journal claim; result policy and correlated audit
precede content loading. One dispatch produces one governed charge plus one root
receipt. No legacy financial writer, tool handler, session append or channel send
runs here. Warn preserves output; configured block caps use the inclusive root
cost. A successful view includes the original receipts and step: neutral text,
toolCalls, reasoning, providerBlocks, stop and an order index preserving their
interleaving. Text ranges use UTF-16 offsets; adjacent text deltas are coalesced. This is backstage harness data,
not a public reply. The adopting loop must hide reasoning and authorize tools.
Opaque JSON is preserved; invalid/non-JSON/oversized output cannot authorize tools.
Malformed tool arguments retain an empty input and boolean marker, never raw text.
Why output is invalid (spec: invalid-output-reason). The run that settles a
call invalid carries invalidReason, the first check the collector refused,
in a closed name (INVALID_OUTPUT_REASONS): output_chars, reasoning_chars,
text_malformed, tool_call, block_malformed, stop_conflict or
event_type. It never carries content. Nothing stores it: read, replay and
recover have none. Text and tool/provider block JSON count toward
maxOutputChars; reasoning block JSON (an OpenAI encrypted_content and its
summary) counts toward maxOutputChars alongside text and tools, at most
1,000,000 (spec: remove-reasoning-ceiling). Optional maxConsecutiveReasoning detects
a step whose model emits more than that many consecutive reasoning events
without any text_delta or tool_call: the collector marks the output
invalid with cause reasoning_loop on the first event past the limit
(spec: reasoning-loop-detection), and the runner stops reading the stream
there (spec: loop-abort-unsettled). Providers report usage only at the end,
so the cut call's cost is unknown: it ends reconciliation_required, with no
settlement or root receipt, for the operator to reconcile, and that run's view
carries invalidReason (the first refusal's name). When the terminal usage
came before the cut, the call settles invalid with its cost. Any other
refusal is read to its end. An adapter that ignores its signal is read to its
end or to runTimeoutMs.
read({scope,rootKey,operationKey}) and recover(key,{leaseMs}) bind the call to
its registered root. Both financial receipts are required for completed status;
recover repairs missing aggregation even if the journal is already complete.
Recovery loads no request and dispatches no provider/tool.
A fresh run checks the records its own writes returned instead of reading them
back, and reads only the content again (spec: turn-rereads). Lost output becomes
unavailable; erased/expired content cannot be regenerated. Root closure prevents
new admission but does not cancel a previously reserved call before its deadline.
The root owner closes/reconciles roots and owns whole-turn/effect uncertainty.
reconcileUsage({scope,rootKey},query?) acknowledges exact evidence only when that
root's completion/accounting (or not-dispatched proof) is durable. It scans bounded
scoped inbox pages; restart subsequent passes to see late commits. The public run
is bounded by runTimeoutMs (max ten minutes), external abort and, for new calls,
the original deadline. Its background drain reports metadata-only failures.
The conversation package adopts this step; legacy runners remain separate.
isStepInterruption(error) is true only of a rejection this runner raised itself
(its timeout, its deadline or its caller's signal) while no store write of the
stream had failed; a store's own AbortError is not one (spec: call-retry).
See spec.
A clean attested Anthropic context rejection stores a metadata-only refusal after the original zero settlement and root receipt. Any content (even rejected by the collector), missing reference, repeated/different stop or stream failure invalidates the proof. Accounting recovery never resumes a conversation automatically (spec: safe-context-rejection-rotation).
A pre-stream rejection proof (spec: pre-stream-rejections) needs a reference,
no content, no stop and the stream's failure. The call then completes with
outcome unavailable and stopReason: null, settled at zero with its root
receipt, in the text, structured and step runners. These calls used to read
reconciliation_required. Any other combination with the proof is
unknown/invalid.
Explicit tool selection
Set the same toolChoice on call.execution.controls and the lazy ModelRequest,
for example {type:"tool",name:"extract_record"}. The request's tools must come
from the host-authorized registry. The primitive validates the selected name
against these actual ToolSpecs even when a custom client validator does nothing;
provider validation additionally applies its documented support matrix before
network access. Selection never executes a tool or grants permission to do so.
Choices are immutable execution metadata. An identical replay returns original
receipts and retained output without loading input; a changed choice (including
absent versus explicit auto) under the same identity conflicts. Accounting recovery
never dispatches another request. Older records without toolChoice remain readable.
The text/JSON createSingleCallRunner rejects toolChoice on configuration or run
input; use createGovernedStepRunner for structured tool responses.
A completed view alone is insufficient: inspect result.outcome, step.stop, the
expected name in step.toolCalls and its malformed marker before validating its
arguments against the business schema. Refusal, truncation, unavailable output and
malformed arguments must not be treated as a valid extraction. Non-success views
may withhold partial blocks while retaining the original stop and financial receipt.
Structured single calls
createStructuredCallRunner(config) is the one-call structured response with the
text runner's call shape: run({scope, sessionId, operationKey, inputRevision, intent, loadInput, signal?}). Use it for a forced tool acting as an extraction
schema. Configuration is the text runner's, without output, plus trees,
accounting, trusted registry tools and an optional toolChoice. Tool names
are validated and deduplicated at construction.
The runner owns the operation root, the identities, the replay check and the
closure. Every identifier is alma-structured-v1:<kind>:<sha256> over the scope
and the host key: bounded, domain-separated and never embedding the key. That
prefix is reserved; do not use it for other keys. Replay compares the root's
immutable binding without its deadline, so no occurrence time is needed. The
binding covers session, intent, input revision and a digest of all configuration
data, including prices and tools. Changes in trusted policy.resolve behavior
cannot be digested: bump policyVersion. The session/day block preflight runs
before any root exists.
A completed view is the unchanged governed step view: inspect result.outcome,
step.stop, the selected call and its malformed marker before using it. A
failed root closure never hides a completed result: closure is detached housekeeping, and an unclosed root expires and is reconciled. Without an execution record,
read reports not_dispatched only for a non-active root with zero reserved
calls, proven by the store under its root lock. A reserved call without a
journal record reads reconciliation_required; an active root reads
in_progress, whatever the application clock says. One key is one call:
unusable outcomes are final, so retry with a new key.
recover(scope, key, {leaseMs}) and reconcileUsage(scope, key, query?) take the
host key. reconcileExpired(scope, {limit?}) is scope-wide and returns {roots, executions}. See spec.