Documentation home

SDK recording and evidence context

Use the observation API to record a supported message or event without creating policy authority. Python haltstate-sdk==0.8.0.dev1 is available from PyPI and includes synchronous and asynchronous record_observation(...) helpers. Go and Rust dev1 recording previews are also publicly available and clean-consumer verified. Java's dev1 release is available through JitPack and a clean Java 17 consumer compiled successfully. The TypeScript dev1 recording helper remains an unpublished source candidate.

Existing credentials, explicit capture

Use the same configured HaltState client, tenant key and registered agent that your integration already uses. No separate customer signing key or EID setup is required for observation or attachment submission. Connected recording uses enabled / all_except / 90-day settings by default, with explicit per-agent exclusions. Older saved selected_only rows remain compatible. The server's effective setting is authoritative.

Mint one stable activity_id for each natural operation and reuse it across its check, guard, report, observation and attachment calls. Optional evidence context may also carry correlation, conversation, message, customer, request and operation identifiers. A supplied operation ID is linked only after same-tenant, same-agent validation. These are audit metadata, not extra tool parameters or a way to change policy decisions, operation fingerprints or replay selection.

These helpers do not intercept model providers, record hidden reasoning or copy whole conversations. The application explicitly submits only supported connected message or business-event content that it is permitted to retain.

Public helper names

SDK Record a message/event without policy authority Evidence-aware check Capture/report helpers
Python record_observation(...) (sync and async) check(..., evidence_context=...) attach_evidence(...), attach_evidence_for_guard(...); report returns an optional receipt
TypeScript/JavaScript recordObservation(...) check(..., evidenceContext) attachEvidence(...), attachEvidenceForGuard(...); report returns optional capture state
Go RecordObservation(...) CheckWithEvidence(...) AttachEvidence(...), AttachEvidenceForGuard(...), ReportWithEvidenceCapture(...)
Rust record_observation(Observation { evidence_context: ObservationContext, ... }) Existing check_with_evidence(...) / guard_with_evidence(...) unchanged check_with_evidence_capture(...), guard_with_evidence_capture(...), report_evidence_guard_outcome_with_capture(...), report_with_evidence_capture(...)
Java recordObservation(...) check(..., EvidenceContext) attachEvidence(...), attachEvidenceForGuard(...), reportWithCapture(...)

Go and Rust return additive evidence-aware wrappers; existing public check result types remain compatible. Rust specifically preserves the original field shapes of EvidenceContext, EvidenceCheckResult, EvidencePermit and GuardReceipt for existing exhaustive struct literals. New ExtendedEvidenceContext, ObservationContext, EvidenceCheckCapture, EvidencePermitCapture and GuardOutcomeCapture carry activity/operation identity and capture receipts. The capture-aware methods are additive; callers may keep using the old methods without source changes. The API models and context contract define the exact request/response fields and guard/report integration names. Python's async client supports the check-result path; the guard helper belongs to its existing synchronous guard client. Guard-result helpers use the persisted evidence ID returned by the server. They do not manufacture an evidence record or run an extra policy check. Keep using the existing guard/start/outcome sequence for action authority; attachment submission does not replace any of those steps.

record_observation/recordObservation is for a supported message or event boundary that may exist outside a policy call. It sends agent_id, stable event_id, action, direction (input, output or event), optional timezone-aware occurrence time, allowlisted evidence context, canonical {kind,value} external references, and optional base64 content. Content is rejected locally above 256 KiB. The receipt must say record_state=recorded and governance_state=not_evaluated; the SDK never converts that observation to ALLOW, a permit or proof of execution.

Retries reuse the exact event ID and payload, and are limited to transport failure, 429 and 5xx. A 4xx conflict, malformed success or missing durable receipt is not labelled recorded. Automatic hooks attach only non-empty title/content inputs and public delivery identifiers/URLs on output. They do not retain credentials, prompts, history, hidden reasoning or arbitrary provider responses, and a capture failure never reruns the policy call, business action or report.

Published preview IDs and October 2 release state

SDK Existing published preview October 2 dev1 state
Python PyPI haltstate-sdk==0.8.0.dev0 0.8.0.dev1 published on PyPI; exact wheel/sdist hashes and a clean Python 3.12 public install verified. Stable 0.7.0 is unchanged.
TypeScript/JavaScript npm @haltstate/sdk@2.0.0-dev.0 under next 2.0.0-dev.1 source candidate; not yet publicly released. Stable latest=1.0.2 and next=2.0.0-dev.0 remain available.
Go GitHub/module preview v1.1.0-dev.0 v1.1.0-dev.1 published; clean Go module-proxy resolution and tests passed.
Rust GitHub Cargo tag v0.2.0-dev.0 v0.2.0-dev.1 published; clean Git dependency resolution and consumer compilation passed. Distribution remains Git-based, not crates.io.
Java GitHub/JitPack tag v0.3.0-dev.0 v0.3.0-dev.1 published; JitPack build/tests and a clean Java 17 Gradle consumer passed.

Python, Go, Rust and Java dev1 recording previews have verified public consumers. The TypeScript row describes source API shape, not a registry installation promise. Published dev0 versions predate the recording helpers and must not be described as containing them. Stable/default channels remain unchanged.

Python example

Record a supported message boundary with a stable event and activity identity:

receipt = client.record_observation(
    agent_id="signal-moltbook",
    event_id="conversation-42:message-7:input",
    action="customer.message.received",
    direction="input",
    evidence_context={
        "activity_id": "activity-42-message-7",
        "conversation_id": "conversation-42",
        "message_id": "message-7",
        "customer_reference": "customer-482",
    },
    external_references=[{"kind": "message_id", "value": "message-7"}],
    content=b"The message explicitly selected for retention",
)
if receipt.record_state != "recorded":
    handle_evidence_gap(receipt.error)

Here client is the application's existing configured client and checked is the evidence-aware result from its existing policy-check workflow. This example only submits evidence; it does not execute or authorize the underlying action.

receipt = client.attach_evidence(
    checked,
    attachment_id="case-482:input:1",
    content="The governed input to retain".encode("utf-8"),
    content_kind="input",
)
if receipt.state != "available":
    # Record a capture gap using the state/reference, not private message text.
    handle_evidence_gap(receipt.state)

The async client uses the same arguments with await.

TypeScript example

const receipt = await client.attachEvidence(
  checked,
  'case-482:input:1',
  new TextEncoder().encode('The governed input to retain'),
  'input',
);
if (receipt.state !== 'available') {
  handleEvidenceGap(receipt.state);
}

Retry, retention and access

Use a stable attachment ID for the same evidence, agent, content kind and bytes. A retry returns the original reference, digest and expiry, with current availability. Reusing the ID for conflicting content is an error. Use distinct input and output IDs. An expired retry does not recreate a body or extend its retention period.

Retention is selected at upload: 30, 90 or 365 days, defaulting to 90. A later setting change applies to new uploads. Capture failure or expiry remains separate from the action decision and must not be reported as successful evidence capture. Do not retry the business action merely because an attachment failed.

Upload permission does not grant read access to other retained messages. Investigation content and exports use separate account/connection permissions. Never log submitted content, API credentials or full diagnostic response bodies.

For the customer investigation workflow and assurance limits, see Investigating actions with Proof Packs.