Skip to main content

LangChain callback that anchors agent decisions on BSV via Satsignal.

Project description

langchain-satsignal

LangChain callback that anchors agent decisions on Bitcoin SV via Satsignal.

Why this exists. When an autonomous agent decides something — picks a tool, runs a query, signs off on an output — there's no native record proving what was decided or when. langchain-satsignal wires a BaseCallbackHandler into the four-anchor pattern Satsignal uses for agent runs: a policy snapshot at the start, a commit-reveal anchor per decision, and an evidence-bundle manifest at the end. Auditors get a tamper-evident, offline-verifiable record without needing access to your agent's runtime.

Install

pip install langchain-satsignal

Only requires langchain-core. No SDK. The Satsignal helper is vendored in (stdlib-only) so the dependency tree stays flat.

Quickstart

from langchain_satsignal import SatsignalCallbackHandler, SatsignalConfig

handler = SatsignalCallbackHandler(SatsignalConfig(
    api_key="sk_live_...",     # mint in your Satsignal workspace
    folder_slug="agent-runs",  # workspace folder to anchor under
    #                          # legacy alias: matter_slug="agent-runs"
    agent_name="my-evaluator",
    agent_version="1.0.0",
))

# Pass to any LangChain agent / chain via the callbacks config:
result = executor.invoke(
    {"input": "..."},
    config={"callbacks": [handler]},
)

That's it. The handler:

  • anchors a policy snapshot at the top-level chain start (system prompt + user input hashes),
  • anchors a commit-reveal commitment per ReAct step (default; configurable),
  • anchors an evidence-bundle manifest at the top-level chain end (Merkle root over all decisions).

See examples/react_agent.py for a complete runnable example.

Config

Field Default Notes
api_key (required) Bearer key minted from your Satsignal workspace
folder_slug (required*) Server-side folder; must exist before the first anchor. Preferred name.
matter_slug (required*) Frozen legacy alias of folder_slug — still supported forever
base_url https://app.satsignal.cloud Override for self-hosted / staging
fail_open True API errors logged + swallowed; agent execution continues
decide_on "agent_action" "agent_action" (per ReAct step) / "tool_start" (per tool call) / "chain_end" (final outputs only)
transport None Test hook. Inject a callable to mock HTTP — see "Testing without the network"
session_id top-level run_id Override only if you need a custom correlation key
write_handoff False If True, writes handoff.json on chain end (contains tool inputs/outputs — review for secrets first)
agent_name None Optional metadata baked into the policy snapshot
agent_version None Optional metadata baked into the policy snapshot

* Vocabulary: folder_slug is the preferred public name; matter_slug is a frozen legacy alias and keeps working forever. Supply either one (legacy code passing matter_slug= is unaffected). If both are set to different values the constructor raises. The CLI takes --folder alongside --matter. The HTTP request to the Satsignal API still sends the frozen matter_slug key, so older / self-hosted servers keep working unchanged. handoff.json now carries both folder_slug and matter_slug (and proof_id alongside bundle_id in each anchor record).

decide_on granularity

Value One anchor per… When to use
agent_action ReAct step (LLM decision to act) Default. Strongest "what did the agent decide" claim per anchor
tool_start Each individual tool call Tool-heavy agents where each external effect needs its own timestamp
chain_end The final chain output only Cheapest mode. Manifest still binds the run, but per-decision timing isn't anchored

Cost: ≈ 119 sats / anchor at 0.1 sat/byte BSV fee floor (≈ $0.0001 at $60/BSV in May 2026). A 5-step agent with decide_on="agent_action" runs about $0.0007 in chain fees. See the Satsignal /agents doc for full cost analysis.

Fail modes

fail_open=True (default) is right for most agents — an audit-trail gap is preferable to a halted agent. Errors go to stderr:

[satsignal] anchor failed: HTTP 429: ...; continuing (fail_open=True)

fail_open=False is right for regulated workflows where unanchored decisions are unacceptable. The first API error halts the agent.

A future release will add fail_pending (queue locally, anchor when network recovers); for now that flow needs a custom transport.

Testing without the network

Frameworks that integrate this need to test their wrapper without burning chain fees. The transport= config takes any callable with the signature:

transport(method, url, headers, body_bytes, timeout) -> (status_int, response_bytes)

so your test suite can substitute any HTTP mocking library. Example:

def fake_transport(method, url, headers, body, timeout):
    return 200, b'{"txid":"deadbeef","bundle_id":"abc","root":"f00d","leaf_count":1}'

handler = SatsignalCallbackHandler(SatsignalConfig(
    api_key="sk_test", matter_slug="m", transport=fake_transport,
))

Status codes 4xx/5xx still raise APIError, so the handler's fail_open / fail_closed branches are testable. See tests/test_callback.py for the full pattern (8 tests, run offline in 0.04s).

How the handler maps to LangChain hooks

LangChain callback Satsignal action
on_chain_start (top-level: parent_run_id is None) Build policy snapshot from serialized + inputs, anchor it
on_chain_start (nested) No-op (no double anchoring)
on_agent_action (with decide_on="agent_action") Wrap action in commit-reveal envelope, anchor commitment
on_tool_start (with decide_on="tool_start") Wrap tool input in commit-reveal envelope, anchor commitment
on_chain_end (top-level) (If decide_on="chain_end": anchor final outputs.) Anchor manifest.
on_chain_error (top-level) Skip manifest (chain failed); clean up the session

Concurrency: one handler instance can serve multiple top-level chain runs concurrently — each gets its own session keyed by the top-level run_id. Within a single chain, callbacks fire serially per LangChain's contract.

Limitations (v0.1)

  • Sync handler only. LangChain dispatches sync callbacks in a thread for async chains, so this works for both, but a native async handler is deferred.
  • No streaming token anchors. Per-token anchoring would dominate chain fees and add no audit value.
  • No fail-pending queue mode. See "Fail modes" above.
  • Policy snapshot is heuristic. serialized from LangChain is hashed wholesale; for a tighter snapshot, build one yourself with the underlying _anchor.build_policy_snapshot(...) and anchor it before invoking the chain.

Links

License

MIT. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

langchain_satsignal-0.2.0.tar.gz (22.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

langchain_satsignal-0.2.0-py3-none-any.whl (19.2 kB view details)

Uploaded Python 3

File details

Details for the file langchain_satsignal-0.2.0.tar.gz.

File metadata

  • Download URL: langchain_satsignal-0.2.0.tar.gz
  • Upload date:
  • Size: 22.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for langchain_satsignal-0.2.0.tar.gz
Algorithm Hash digest
SHA256 24504972eec4fdd2bd59eb6d5dc6c2461b7a446e898e21e2f3a68242e88b4bd5
MD5 7f9127c00f248c5d1f1221a083757a7d
BLAKE2b-256 ec0e0a7fabc0adbd87dc30013cef93915e4034b37c99dc825dfd152dab433839

See more details on using hashes here.

File details

Details for the file langchain_satsignal-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for langchain_satsignal-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b0b330b774aaddc519dc2db01106eb48aaae0227d3c03666b46aee7f55225604
MD5 10636c74ee457b41b1197604e6308539
BLAKE2b-256 798f5115903a2a3f90bc632879dc3efb15fb94daca7888e9aff7bddc45e722f9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page