English | 中文
AI coding agents like Claude Code, OpenClaw, and OpenCode generate rich session transcripts — turns, tool calls, MCP interactions, shell commands — but there is no unified way to collect, trace, and observe these events across different runtimes.
Spanory gives you a single CLI and plugin system to parse agent transcripts into a unified event model, then ship them as OpenTelemetry traces to backends like Langfuse — with zero cron, realtime hooks, and offline backfill.
- One CLI, Multiple Runtimes — Unified tracing for Claude Code, Codex, OpenClaw, OpenCode
- OTel-Native Transport — OTLP HTTP export compatible with Langfuse, with extensible backend adapters
- Realtime + Offline — Hook-based live ingestion and CLI-driven session replay/backfill
- Report & Alert — Built-in aggregation views (session, MCP, tool, cache, turn-diff) and rule-based alerting with webhook + CI support
- Cross-Platform — macOS, Linux, Windows with OS-specific hook wrappers
- Pluggable Architecture — Runtime adapters and backend adapters are fully decoupled
RuntimeAdapter → Canonical Events → BackendAdapter → OTLP Core → OTLP HTTP
Unified Event Model (SpanoryEvent):
turn · agent_command · shell_command · mcp · agent_task · tool
| Package | Description |
|---|---|
@bububuger/core |
Normalized schema, parser interfaces, mapping contracts (TypeScript) |
@bububuger/otlp-core |
OTLP compile & send transport |
@bububuger/backend-langfuse |
Langfuse backend adapter |
@bububuger/spanory-openclaw-plugin |
OpenClaw plugin for realtime ingestion |
@bububuger/spanory-opencode-plugin |
OpenCode plugin for realtime ingestion |
@bububuger/spanory |
Local parser, export CLI, hook handler |
To keep implementation style and quality consistent across humans and agents, follow the standards workflow first:
For new feature or bug fix work, this workflow defines:
- Required design updates
- Required test updates
- Required verification gates before merge
Telemetry field governance gate:
npm run telemetry:checkIssue status巡检(自动化任务建议每轮执行):
npm run issue:statusReleases page: https://github.com/Bububuger/spanory/releases
Each release includes:
spanory-<version>-darwin-arm64.tar.gz(macOS Apple Silicon)spanory-<version>-darwin-x64.tar.gz(macOS Intel)spanory-<version>-linux-x64.tar.gz(Linux x64)spanory-<version>-windows-x64.zip(Windows x64)SHA256SUMS.txt
macOS / Linux:
TAG=vX.Y.Z # replace with the target release tag
# macOS arch: arm64 => darwin-arm64, x86_64 => darwin-x64
# Linux (supported): x86_64 => linux-x64
OS_ARCH=darwin-arm64
# quick check on macOS: uname -m
curl -fL -o spanory.tar.gz \
"https://github.com/Bububuger/spanory/releases/download/${TAG}/spanory-${TAG#v}-${OS_ARCH}.tar.gz"
tar -xzf spanory.tar.gz
chmod +x spanory
sudo mv spanory /usr/local/bin/spanory
spanory --helpWindows (PowerShell):
$Tag = "vX.Y.Z" # replace with the target release tag
Invoke-WebRequest -Uri "https://github.com/Bububuger/spanory/releases/download/$Tag/spanory-$($Tag.TrimStart('v'))-windows-x64.zip" -OutFile "spanory.zip"
Expand-Archive -Path "spanory.zip" -DestinationPath ".\\spanory-bin" -Force
.\\spanory-bin\\spanory.exe --helpOptional integrity check:
curl -fL -o SHA256SUMS.txt \
"https://github.com/Bububuger/spanory/releases/download/${TAG}/SHA256SUMS.txt"
shasum -a 256 -c SHA256SUMS.txt# Run directly (no global install)
npx @bububuger/spanory@latest --help
# Global install
npm i -g @bububuger/spanory
spanory --helpcd spanory
npm install
npm install -g ./packages/cli
spanory --helpexport OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:3000/api/public/otel/v1/traces"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic $(printf '%s' '<PUBLIC_KEY>:<SECRET_KEY>' | base64)"OTEL_EXPORTER_OTLP_HEADERS expects k=v pairs (comma-separated if multiple).
For Langfuse OTLP, use Authorization=Basic <base64(public_key:secret_key)>.
install is idempotent and configures all supported runtimes (Codex defaults to non-proxy watch daemon mode):
spanory status
spanory install --runtimes claude-code,codex,openclaw,opencode
spanory doctor --runtimes claude-code,codex,openclaw,opencodeWhat install does:
- Claude Code: writes/updates
Stop+SessionEndhook command tospanory hook --last-turn-only - Codex: removes legacy
~/.codex/bin/spanory-codex-notify.shandnotify = [...]from~/.codex/config.toml, then startsspanory runtime codex watch --last-turn-onlyin background (~/.spanory/codex-watch.pid,~/.spanory/logs/codex-watch.log) - OpenClaw: installs/enables Spanory plugin (when
openclawis available in PATH) - OpenCode: installs Spanory plugin loader into
~/.config/opencode/plugin
Dry-run example:
spanory install \
--runtimes claude-code,codex,openclaw,opencode \
--dry-runPaste this block to your coding agent in this repo, and let it finish setup automatically:
Install and configure Spanory on this machine with one-command setup.
Requirements:
- Keep Codex in watch daemon mode (no proxy hijack, no notify injection).
- Configure all runtimes: claude-code,codex,openclaw,opencode.
- Verify and report any failed checks.
Run:
1) npm install
2) npm install -g ./packages/cli
3) spanory status
4) spanory install --runtimes claude-code,codex,openclaw,opencode
5) spanory doctor --runtimes claude-code,codex,openclaw,opencode
Output:
- status/install/doctor JSON results
- final pass/fail summary and next-step troubleshooting for failed checks
Set hook command in Claude Code config for SessionEnd and/or Stop events:
spanory hookThe CLI reads the hook payload from stdin, parses the session transcript, and ships traces via OTLP automatically.
Recommended: Bind the
Stopevent in addition toSessionEnd.Stopfires on every assistant turn completion, enabling near real-time trace reporting instead of waiting for the session to end.
spanory runtime openclaw plugin install
spanory runtime openclaw plugin enable
spanory runtime openclaw plugin doctorspanory runtime opencode plugin install
spanory runtime opencode plugin doctorThe plugin auto-loads ~/.spanory/.env at runtime (only fills missing env vars), so GUI-launched OpenCode can still pick up:
OTEL_EXPORTER_OTLP_ENDPOINTOTEL_EXPORTER_OTLP_HEADERS
Trigger mode (recommended realtime by turn):
# default: flush on turn/message completion events (realtime)
export SPANORY_OPENCODE_FLUSH_MODE="turn"
# optional: only flush on session lifecycle end events
# export SPANORY_OPENCODE_FLUSH_MODE="session"Diagnostics (when "triggered but not reported"):
# latest plugin status
cat ~/.spanory/opencode/plugin-status.json
# detailed plugin runtime logs
tail -n 120 ~/.spanory/opencode/plugin.log
# structured doctor checks (includes endpointConfigured hint)
spanory runtime opencode plugin doctorCodex runtime supports export/backfill/hook/watch based on ~/.codex/sessions/**/*.jsonl.
# Export one codex session
spanory runtime codex export \
--session-id <SESSION_ID> \
--endpoint "$OTEL_EXPORTER_OTLP_ENDPOINT" \
--headers "$OTEL_EXPORTER_OTLP_HEADERS"
# Batch backfill codex sessions by mtime
spanory runtime codex backfill \
--since 2026-03-01T00:00:00Z \
--limit 50 \
--endpoint "$OTEL_EXPORTER_OTLP_ENDPOINT" \
--headers "$OTEL_EXPORTER_OTLP_HEADERS"Codex notify payload can be consumed for near realtime turn-level export:
echo '{"event":"agent-turn-complete","thread_id":"<SESSION_ID>","turn_id":"<TURN_ID>","cwd":"<PROJECT_CWD>"}' | \
spanory runtime codex hook --last-turn-onlyWhen Codex notify is not firing (or missed intermittently), use watcher fallback for near-realtime polling export:
# long-running watcher, only handles newly updated sessions after startup
spanory runtime codex watch --last-turn-only
# one-shot scan for existing sessions (useful for quick verification)
spanory runtime codex watch --include-existing --once --settle-ms 0Use an OpenAI-compatible local proxy to capture full request/response bodies with strong redaction.
# Start proxy
spanory runtime codex proxy \
--listen 127.0.0.1:8787 \
--upstream https://api.openai.com
# Route Codex traffic to proxy
export OPENAI_BASE_URL="http://127.0.0.1:8787"# Preview sessions to process
spanory runtime claude-code backfill \
--project-id my-project \
--since 2026-02-27T00:00:00Z \
--dry-run
# Run backfill with OTLP export
spanory runtime claude-code backfill \
--project-id my-project \
--since 2026-02-27T00:00:00Z \
--endpoint "$OTEL_EXPORTER_OTLP_ENDPOINT" \
--headers "$OTEL_EXPORTER_OTLP_HEADERS"spanory report session --input-json /path/to/exported.json
spanory report mcp --input-json /path/to/exported-or-dir
spanory report command --input-json /path/to/exported-or-dir
spanory report agent --input-json /path/to/exported-or-dir
spanory report cache --input-json /path/to/exported-or-dir
spanory report tool --input-json /path/to/exported-or-dir
spanory report context --input-json /path/to/exported-or-dir
spanory report turn-diff --input-json /path/to/exported-or-dirspanory alert \
--input-json /path/to/exported-or-dir \
--rules /path/to/rules.json \
--fail-on-alertSupports webhook notifications and CI integration with non-zero exit codes on alert.
| Code | Meaning |
|---|---|
0 |
Command succeeded. |
1 |
Unexpected runtime error (crash / unhandled rejection). |
2 |
Command completed with failed checks or alerts (for example: spanory alert eval --fail-on-alert). |
cd /path/to/spanory
npm install
npm link -w packages/cli
spanory --help- Verify env is loaded:
spanory --help >/dev/null
echo "$OTEL_EXPORTER_OTLP_ENDPOINT"
echo "$OTEL_EXPORTER_OTLP_HEADERS"- Verify header format is Basic auth (not Bearer):
Authorization=Basic <base64(public_key:secret_key)> - Run a dry local export to inspect parsed output:
spanory runtime claude-code export \
--project-id <PROJECT_ID> \
--session-id <SESSION_ID> \
--export-json /tmp/spanory-export.json- Ensure Claude Code hook command is exactly
spanory hook --last-turn-only. - Prefer binding both
StopandSessionEnd. - Check local hook log:
tail -n 100 "$HOME/.claude/state/spanory-hook.log"npm run build:bin
./dist/spanory-macos-arm64 --help
# Build all platforms
bash scripts/release/build-binaries.sh all
# Package release archives locally
npm run package:release-assets -- vX.Y.Znpm install
npm run check
npm test
npm run test:bddCI runs the same gates via .github/workflows/ci.yml.
- CI:
.github/workflows/ci.yml- Runs on
push(main/codex//feat/) andpull_request - Enforces quality gates:
check+test+test:bdd - Builds Linux binary and runs smoke check (
--help)
- Runs on
- CD:
.github/workflows/release.yml- Triggered by tag push:
v*(for examplev0.2.0) - Verifies quality gates before release
- Publishes
@bububuger/spanoryto npm whenNPM_TOKENis configured in thereleaseenvironment NPM_TOKENpath:GitHub Settings > Environments > release- Builds release binaries on Linux/macOS/Windows
- macOS artifacts include both Apple Silicon (
darwin-arm64) and Intel (darwin-x64) - Packages archives (
tar.gz/zip) andSHA256SUMS.txt - Publishes GitHub Release with downloadable assets
- Triggered by tag push:
Internal package @alipay/spanory is published to registry.antgroup-inc.cn.
# 1. Commit your changes
git add -A && git commit -m "fix: describe your change"
# 2. Tag new version (sync-version.mjs reads from git tag)
git tag v0.1.XX
# 3. Build + publish (sync-version → build → tnpm publish, one command)
bash scripts/publish-internal.sh
# 4. Commit version bump generated by sync-version
git add package.json packages/cli/package.json packages/alipay-cli/package.json
git commit -m "chore: bump version to 0.1.XX"
# 5. Push
git push origin main && git push origin v0.1.XXKey points:
- Do NOT manually edit version in package.json —
scripts/release/sync-version.mjsderives it from the latest git tag. publish-internal.shcallssync-version.mjs→ builds@alipay/spanory→ runstnpm publish.- Public npm release (
@bububuger/spanory) is handled by GitHub Actions CD on tag push (v*).
See docs/ROADMAP.md for the full project evolution and future plans.
See CONTRIBUTING.md for guidelines.