Observability
How Syntropic137 collects agent events: two-channel pipeline, event types, and how to verify everything is flowing.
Syntropic137 captures every agent action in real time. Events flow from workspace containers through the collector service into TimescaleDB, where projections build the read models that power the dashboard.
Two-Channel Pipeline
Workspace containers emit observability data through two complementary channels:
| Channel | Transport | What It Captures |
|---|---|---|
| Plugin hooks | JSONL to stderr, captured by the orchestrator | Session lifecycle, subagent tracking, tool start/end, git operations, context compaction, permissions |
| Native OTel | OTLP HTTP push to syn-collector | Per-API-call cost and model, cache token breakdown, API errors, active time, lines of code |
Both channels flow through syn-collector (/events for plugin hooks, /v1/metrics + /v1/logs for OTel) and are stored in the same agent_events table in TimescaleDB.
Why two channels?
OTel can't capture everything: subagent lifecycle, context compaction, and granular git operations (branch, merge, push) are not exported by Claude Code's OTel implementation. Conversely, plugin hooks can't give you per-API-call cost breakdowns or retry counts. The channels are complementary:
| Event | Source |
|---|---|
| Subagent started / stopped | Plugin hooks only |
| Context compaction (before/after tokens) | Plugin hooks only |
| Git commit, push, merge details | Plugin hooks only |
| Per-API-call cost + model | OTel only |
| API errors (status code, retries) | OTel only |
| Cache token breakdown | OTel only |
| Token usage totals | Both (deduplicated) |
| Tool execution completed | Both (deduplicated) |
Event Flow
Workspace Container
├── Plugin hooks → stderr → orchestrator captures JSONL → POST /events
└── Claude Code OTel → OTLP JSON push → POST /v1/metrics, /v1/logs
↓
syn-collector
(dedup, store, batch)
↓
TimescaleDB
(agent_events hypertable)
↓
Projections
(session_cost, tool_timeline,
execution_cost, realtime SSE)
↓
Dashboard + APIEvent Types
Plugin Hook Events (JSONL channel)
| Event Type | Trigger | Key Data |
|---|---|---|
session_started | Agent session begins | source, cwd, permission_mode |
session_ended | Agent session ends | reason, duration_ms |
agent_stopped | Agent stopped | reason |
subagent_started | Task tool invoked | subagent_id |
subagent_stopped | Task tool completed | subagent_id, reason |
tool_execution_started | PreToolUse hook | tool_name, tool_use_id, input_preview |
tool_execution_completed | PostToolUse hook | tool_name, duration_ms, success |
pre_compact | Context compaction | before_tokens, after_tokens, reduction_percent |
git_commit | post-commit hook | sha, branch, files_changed, insertions, deletions |
git_push_started | pre-push hook | remote, branch, commits_count |
git_merge_completed | post-merge hook | branch, merge_sha |
user_prompt_submitted | UserPromptSubmit hook | prompt_preview |
OTel Events (OTLP channel)
| Event Type | OTel Source | Key Data |
|---|---|---|
token_usage | claude_code.token.usage metric | value, type (input/output/cache) |
cost_recorded | claude_code.cost.usage metric | value (USD) |
api_request | claude_code.api_request log | model, cost_usd, duration_ms, input_tokens, output_tokens, cache_read_tokens, speed |
api_error | claude_code.api_error log | model, error, status_code, duration_ms, attempt |
tool_execution_completed | claude_code.tool_result log | tool_name, success, duration_ms |
otlp_session_count | claude_code.session.count metric | (counter increment) |
otlp_commit_count | claude_code.commit.count metric | (counter increment) |
Collector Configuration
syn-collector receives events from workspace containers. It must be reachable on the Docker network that workspace containers use (agent-net).
The collector endpoint is auto-injected into workspace containers by the orchestrator when COLLECTOR_URL is set in syn-api's environment:
# In your .env / environment config
COLLECTOR_URL=http://collector:8080When COLLECTOR_URL is set, every workspace container gets:
CLAUDE_CODE_ENABLE_TELEMETRY=1: activates Claude Code's OTel exportOTEL_EXPORTER_OTLP_ENDPOINT=http://collector:8080: directs OTel push to the collector
If COLLECTOR_URL is not set, OTel export is a graceful no-op: containers still emit plugin hook events via the JSONL channel.
Verifying Events Are Flowing
Health check
curl http://localhost:8080/health
# → {"status": "healthy"}Check recent events
curl -s http://localhost:8080/events/recent | \
jq 'group_by(.event_type) | map({type: .[0].event_type, count: length})'After a workflow execution you should see both channels represented:
[
{"type": "api_request", "count": 12},
{"type": "cost_recorded", "count": 3},
{"type": "git_commit", "count": 2},
{"type": "session_started", "count": 1},
{"type": "subagent_started", "count": 2},
{"type": "token_usage", "count": 18},
{"type": "tool_execution_completed", "count": 24}
]Verify collector is reachable from agent-net
docker run --rm --network syn-dev_agent-net curlimages/curl \
curl -s http://collector:8080/health
# → {"status": "healthy"}Token Cost Optimization
Workspace images include built-in optimizations that reduce token consumption, and the observability pipeline captures the evidence.
RTK Bash Compression
RTK is pre-installed in workspace images and intercepts Bash tool calls. It compresses output from commands like ls, find, git log, and tree into token-efficient formatting. Claude Code uses these aliases transparently, no configuration needed.
Measured impact from A/B testing on an "explore repository" task:
| Metric | Without RTK | With RTK | Savings |
|---|---|---|---|
| Context tokens | 61,117 | 28,663 | 53% |
| Cost (USD) | $0.13 | $0.09 | 29% |
| Conversation turns | 12 | 6 | 50% |
RTK primarily reduces input tokens, the largest cost driver. This shows up in the OTel channel's api_request events: compare input_tokens vs cache_read_tokens to see how much context the model is consuming per call.
Observing the Impact
With both channels active, you can track token efficiency per session:
api_requestevents (OTel):input_tokens,output_tokens,cache_read_tokensper API call. Lowerinput_tokensrelative to task complexity indicates RTK compression is working.token_usageevents (both channels): cumulative input/output/cache totals per turn.cost_recordedevents (OTel): running USD cost. Compare sessions with similar tasks to measure savings.
The dashboard's session cost view aggregates these into per-session and per-execution cost breakdowns.
Deduplication
Events are identified by a deterministic SHA256 event_id derived from content (session ID, metric name, timestamp, index). The collector deduplicates within a sliding window, safe to retry any event submission without double-counting.
Syntropic137 Docs v0.25.4 · Last updated March 2026