Skip to content

Quickstart

Time to first trace: about one minute.

Claude Code invokes tools constantly — file reads, edits, bash commands, web fetches, MCP servers. With four lines of config, every tool call becomes a governance event on disk.

Step 1 — Wire the hook

Create or edit ~/.claude/settings.json (user-global) or .claude/settings.json in a project:

// Add this block exactly as-is
{
"hooks": {
"PreToolUse": [
{"matcher": "", "hooks": [{"type": "command", "command": "sentience-claude-code-hook"}]}
],
"PostToolUse": [
{"matcher": "", "hooks": [{"type": "command", "command": "sentience-claude-code-hook"}]}
]
}
}

Claude Code now invokes sentience-claude-code-hook before and after every tool call.

Step 2 — Run Claude Code as normal

Open Claude Code. Do whatever you would normally do — ask it to read a file, run a test, edit some code.

Exit when done.

Step 3 — Review what happened

Terminal window
sentience status # did the hook actually fire?
sentience list # which sessions exist?
sentience open --latest --summary # one-screen view of the latest session

You should see:

  • Sessions listed
  • Events captured
  • A summary explaining what the agent did

If you see nothing → Troubleshooting.

Per-session traces persist at ~/.sentience/traces/claude-code/.

What you will see

Every Bash, Edit, Write, Read, Grep, Glob, WebFetch, WebSearch, and mcp__<server>__<tool> call Claude Code made, evaluated against the default policy rules.


Advanced: Agent wrapper

If you’re just evaluating Sentience, skip this section.

Time to first trace: about three minutes.

If you’re building with the MCP SDK or LangChain, wrap the agent once and every tool call becomes a governance event.

MCP client

from sentience_governor.cache.cache import InProcessCache
from sentience_governor.session_manager.manager import SessionManager
from sentience_governor.sink.writer import SinkWriter, StdoutSink
from sentience_governor.wrapper.mcp import (
SentienceMCPAdapter,
wrap_mcp_client,
)
session_manager = SessionManager()
cache = InProcessCache()
sink = SinkWriter(StdoutSink())
adapted = SentienceMCPAdapter(
delegate=your_sdk_client,
call_fn=lambda client, name, args: client.call_tool(name, args),
)
wrapped = wrap_mcp_client(
target=adapted,
session_manager=session_manager,
cache=cache,
sink_writer=sink,
agent_id="my-agent",
agent_version="1.0.0",
vendor_id="my-company",
declared_capabilities=["crm.read"],
owner_claim="user_123",
stated_objective="Generate Q1 customer report",
)
async with wrapped:
result = wrapped.send_tool_call("crm.get_customer", {"id": "123"})

Three steps: import, assemble, wrap.

LangChain

SentienceCallbackHandler duck-types LangChain’s BaseCallbackHandler, so you attach it as a callback on any LangChain-based agent.

from sentience_governor.cache.cache import InProcessCache
from sentience_governor.session_manager.manager import SessionManager
from sentience_governor.sink.writer import SinkWriter, StdoutSink
from sentience_governor.wrapper.langchain_adapter import SentienceCallbackHandler
session_manager = SessionManager()
cache = InProcessCache()
sink = SinkWriter(StdoutSink())
handler = SentienceCallbackHandler(
agent_id="my-agent",
session_manager=session_manager,
cache=cache,
sink_writer=sink,
agent_version="1.0.0",
declared_capabilities=["crm.read"],
owner_claim="user_123",
)
agent.invoke(
{"input": "Generate Q1 customer report"},
config={"callbacks": [handler]},
)

For create_react_agent, SentienceMiddleware wraps the same handler:

from sentience_governor.wrapper.langchain_adapter import SentienceMiddleware
middleware = SentienceMiddleware(handler)
agent = create_react_agent(llm=llm, tools=tools, prompt=prompt, middleware=[middleware])

Token tracking (optional)

SentienceCallbackHandler automatically captures per-turn LLM token usage from LangChain responses via on_llm_start and on_llm_end and attaches it to subsequent tool-call events. Each turn carries a stable llm_turn_id so multi-tool-call sessions stay mathematically correct under aggregation (consumers must dedupe by (session_id, llm_turn_id) before summing token fields).

For create_react_agent, the same SentienceMiddleware instance also exposes awrap_step — register it to aggregate token usage across messages within a single LangGraph step. The middleware’s existing awrap_tool_call continues to work unchanged for users who don’t opt in.

Anthropic via LangChain carries token data on both usage_metadata (canonical fields) and response_metadata['usage'] (cache fields). The handler merges both shapes so cache reads/writes are preserved.

Sinks — where events go

  • StdoutSink — prints every event to stdout
  • FileSink — append NDJSON to a file
  • HttpLocalSink — POST each event to a local HTTP endpoint

All three are fail-open.

View the trace

Terminal window
my-agent | sentience-cli # pipe from stdout-sink
sentience-cli agent-trace.jsonl # read from FileSink output

Optional: Sentience Sync

Time to first upload: about two minutes.

Sentience Sync is the optional cloud bridge. It reads your trace files, computes aggregated counts per rule, and uploads the counts (not raw events) to Sentience Cloud.

Explicitly manual. No daemon, no watcher, no auto-sync.

Step 1 — Initialize

Terminal window
sentience-sync init

Writes a default config at ~/.sentience/sync-config.json. Edit log_sources to point at your NDJSON trace files.

Step 2 — Register

Terminal window
sentience-sync register --email you@example.com --name "Your Name"

Email and name are required because Sync is an opt-in communication channel. Contact info is stored on the server; never sold, shared, or used for marketing without further consent.

On success, your local state file receives:

  • An installation_id (UUID generated locally)
  • An installation_secret (bearer token issued by the server)
  • Your contact_email and contact_name

State lives at ~/.sentience/sync-state.json with owner-only permissions on POSIX (0600).

Step 3 — Run

Terminal window
sentience-sync run

Reads new events from your configured log_sources, computes per-rule counts for the window since the last run, and uploads the counts.

The payload contains no raw event content — see Sync privacy.

Step 4 — Check for updates (optional)

Terminal window
sentience-sync update-check

Returns any active update notices for your installed version. Does not download or auto-apply anything.

Step 5 — Point Sync at your traces

Sync reads from the file paths in log_sources (set via sentience-sync init or ~/.sentience/sync-config.json).

If you use the MCP wrapper or LangChain callback:

Your agent writes to whatever SinkWriter you configured. Point log_sources at that path.

{
"log_sources": ["/var/log/sentience/agent.jsonl"]
}

If you use the Claude Code hook:

By default, the hook writes one file per session to ~/.sentience/traces/claude-code/<session_id>.jsonl. Sync needs static paths in log_sources, so per-session files aren’t a clean fit.

Set the hook to append to a single file instead:

Terminal window
export SENTIENCE_CLAUDE_CODE_SINK_PATH=/var/log/sentience/claude-code-unified.jsonl

Add that path to log_sources. Every Claude Code session from then on appends to it.

Both together:

Sync aggregates across every file in log_sources. One run counts rule fires from MCP + LangChain + Claude Code combined into a single counts_by_rule payload.

{
"log_sources": [
"/var/log/sentience/agent.jsonl",
"/var/log/sentience/claude-code-unified.jsonl"
]
}

Next


Stuck? → Troubleshooting

© 2026 Crescere Labs, Inc. All rights reserved.