Skip to main content

dev-session-debug

Debugging session.db and the telemetry pipeline. Use when inspecting a session ledger, diagnosing missing telemetry, or correlating events across tables.

Quellinformationen

Repository
google/capsem
Letzte Quellaktivität
25. September 2026 um 15:15
Erkannte Sprache von SKILL.md
Englisch
Sterne
79
Forks
15

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
dev-session-debug
description
Debugging session.db and the telemetry pipeline. Use when inspecting a session ledger, diagnosing missing telemetry, or correlating events across tables.
# Session Database Debugging Every Capsem VM session produces a ledger in its session directory: `~/.capsem/run/sessions/<id>/` for an ephemeral VM, `~/.capsem/run/persistent/<name>/` for a named one. The ledger is two files that only make sense together: - `session.db` -- SQLite, every telemetry table plus the index of captured bodies. - `session.bodies` -- the append-only body archive: HTTP, model, tool, exec and security payloads, deflated in blocks of about 1 MiB. SQLite never holds body bytes. Copy, fork or delete them as a pair; either one alone is useless. A global `~/.capsem/sessions/main.db` aggregates stats across sessions. The session ledger has no migrations. A `session.db` written by an older build fails to open, loudly and by name, rather than being upgraded in place. ## Session identity invariant `<id>` is the opaque VM/session id used for route paths, DB-handle keys, session-directory names, `CAPSEM_VM_ID`, and UI tab routing. Human names such as `co-work1` or `code-vm1` are display aliases only and must live in `name` fields. User surfaces may accept a name (`capsem resume co-work1`), but they must translate it to the VM id before calling `/vms/{id}/...`. Never use a persistent registry name as `SandboxInfo.id`, never look up telemetry routes with `registry.get(&id)` unless the id has first been resolved to the registry key, and never key `session_db_handles` by display name. If a UI shows telemetry for the wrong provider/model, check this boundary first: `/vms/list` and `/vms/{id}/info` must expose `id = session id` and `name = display name`, and `/vms/{id}/stats/detail` must open the DB under the resolved session directory for that id. A session named `co-work1` showing another session's `ollama` rows while its own DB has AGY/Google rows is a route identity bug until proven otherwise. ## Quick inspection ### Listing sessions Python here means the build-system environment: `uv run --project build_system --frozen python ...` (a bare `python3` may be a system interpreter without the packages these need). ```bash python3 build_system/scripts/doctor/list_sessions.py # Recent sessions python3 build_system/scripts/doctor/list_sessions.py -n 20 # Show more python3 build_system/scripts/doctor/list_sessions.py --with-model # Only sessions with AI model calls python3 build_system/scripts/doctor/list_sessions.py --with-db # Only sessions whose ledger is still on disk python3 build_system/scripts/doctor/list_sessions.py --with-net # Only sessions with network events python3 build_system/scripts/doctor/list_sessions.py --with-tools # Only sessions with tool calls python3 build_system/scripts/doctor/list_sessions.py --min-cost 0.01 # Only sessions that cost money python3 build_system/scripts/doctor/list_sessions.py --with-db --with-model # Combine filters ``` Output columns: ID, Created (MM-DD HH:MM:SS), Duration, Cost, net events, tokens (in+out), tool calls, fs events. Sessions with `*` after the ID still have a `session.db` on disk (queryable). Stats come from the main.db rollup, so they're available after the session directory is gone. ### Deep inspection ```bash python3 build_system/scripts/doctor/check_session.py # Full integrity check on latest session python3 build_system/scripts/doctor/check_session.py <id> # Specific session or named VM python3 build_system/scripts/doctor/check_session.py --db path/session.db # A ledger file directly python3 build_system/scripts/doctor/check_session.py --verify-bodies # Also read every body back through its hash python3 build_system/scripts/doctor/check_session.py -n 10 # Show 10 preview rows per table ``` Checks: SQLite page integrity (`PRAGMA quick_check`, first -- a damaged file ends the report), table existence, row counts, body archive integrity (below), file-monitor overflow windows, tool lifecycle integrity (orphaned tool_calls/tool_responses), AI provider correlation (net_events vs model_calls), and NULL detection in critical fields. It exits 1 when the ledger is damaged. Route readiness (`DbHandle::ready`) checks the schema's shape only, once per `schema_version`, and never scans pages: that scan cost 1.8 s per failed poll on a million-row ledger (google/capsem#230). Page damage surfaces as a `malformed` error on the query that reads it, at the ledger copy's `quick_check`, and here. The body archive check compares the index with the file without trusting either: every `event_body_blobs.block_offset` exists in `body_blocks`; every `body_offset + body_len` fits in its block's `raw_len`; `session.bodies` is at least as long as the largest `block_offset + disk_len` (a block's committed extent, headers included) and starts with the archive's file header. `--verify-bodies` then reads every body, inflating each segment once, and checks it against `body_hash`. ## Session database tables (session.db) The model/tool contract is intentionally one ledger: - `model_calls` is one row per model exchange: request sent to the provider and response received from it. - `model_items` is the ordered item ledger for request, reasoning/thinking, response, tool_call, and tool_response content inside those exchanges. - `tool_calls` is the canonical user/security tool-call ledger for all origins (`native`, `mcp`, `builtin`, `local`). User-facing tool counts and CEL tool evidence come from this table. - `tool_responses` records tool result content sent back to a model. A response row must match a `tool_calls.call_id` in the same trace. - MCP protocol facts are typed security events. MCP-origin `tools/call` activity must appear in `tool_calls` with `origin = 'mcp'`. - One `model_calls.id` can emit many `tool_calls.call_id` values. The tool response must reuse the same `call_id`; MCP can enrich that same logical call, but it does not create a second product ledger. ### Identity Graph Use this graph when correlating model, tool, and security rows: ```mermaid flowchart TD Session["session_id<br/>one Capsem session database"] Trace["trace_id<br/>causal runtime chain"] Turn["turn_id<br/>one user-visible agent turn<br/>user input + all resulting work"] ModelA["model_call_id A = model_calls.id<br/>one provider exchange<br/>one request + one response"] ModelB["model_call_id B = model_calls.id<br/>later provider exchange<br/>same user-visible turn"] ModelAItems["model_items for model_call_id A<br/>request, reasoning, response, tool_call"] ModelBItems["model_items for model_call_id B<br/>tool_response input, request, reasoning, response"] ToolA1["tool_call_id A1 = tool_calls.call_id<br/>logical tool invocation"] ToolA2["tool_call_id A2 = tool_calls.call_id<br/>logical tool invocation"] ToolA1Response["tool_responses.call_id = A1<br/>same tool_call_id"] ToolA2Response["tool_responses.call_id = A2<br/>same tool_call_id"] McpFacts["MCP transport facts<br/>origin/type enrichment<br/>same tool_call_id, no duplicate ledger"] EventRows["event_id rows<br/>http, dns, model, tool, file, process, credential, security"] BodyBlobs["event_body_blobs<br/>index by event_id into session.bodies"] SecurityRows["security_rule_events<br/>rule matches by event_id"] ProviderIds["provider response_id / message_id / transport ids<br/>metadata only"] Session --> Trace Trace --> Turn Turn -->|"contains 1..N"| ModelA Turn -->|"contains 1..N"| ModelB ModelA -->|"owns ordered rows"| ModelAItems ModelB -->|"owns ordered rows"| ModelBItems ModelA -->|"emits 0..N"| ToolA1 ModelA -->|"emits 0..N"| ToolA2 ToolA1 -->|"response reuses id"| ToolA1Response ToolA2 -->|"response reuses id"| ToolA2Response McpFacts -.->|"enriches"| ToolA1 McpFacts -.->|"enriches"| ToolA2 ToolA1Response -->|"can feed later exchange"| ModelB ToolA2Response -->|"can feed later exchange"| ModelB Trace --> EventRows Turn --> EventRows ModelA --> EventRows ModelB --> EventRows ToolA1 --> EventRows ToolA2 --> EventRows EventRows --> BodyBlobs EventRows --> SecurityRows ModelA -.-> ProviderIds ModelB -.-> ProviderIds ``` Definitions: - `event_id` identifies one emitted ledger event row. Security rows, body blobs, and event detail routes join back through this id. - `trace_id` groups runtime work caused by one causal operation across tables: HTTP, DNS, model, tool, file, process, credentials, and security. - `turn_id` groups all work caused by one user-visible agent turn: the user's input, every provider exchange needed to answer it, every tool request and response, and every emitted HTTP/DNS/file/process/security row caused by it. - `model_call_id` is the `model_calls.id` value for exactly one provider request/response exchange inside a turn. It owns that exchange's request, reasoning/thinking, response, model-emitted tool-call items, token counts, and provider metadata. It is not the whole user turn; a single `turn_id` can contain multiple `model_call_id` values. - `tool_call_id` identifies one logical tool invocation across model-native tools, MCP transport, Capsem built-ins, and local tools. In SQLite it is stored as `tool_calls.call_id` and `tool_responses.call_id`. Provider response ids, message ids, and transport request ids are provider or transport metadata. They are not Capsem's join contract. One `turn_id` is the user-input scope. It can contain multiple `model_call_id` values when an agent calls the model, executes tools, then calls the model again with tool results. One `model_call_id` is one provider-exchange scope and carries that exchange's request, reasoning/thinking, response, token counts, and ordered `model_items`. It can emit zero or more `tool_call_id` values; this is the canonical one-to-many relationship for model-visible tools. Stated as the debugging invariant: one `model_call_id` can emit N `tool_call_id` values, and each emitted tool response must reuse that `tool_call_id`. A tool response must carry the same `tool_call_id` as the tool request. MCP is not a second user-facing tool ledger. MCP-origin `tools/call` activity must resolve to a `tool_calls` row with `origin = 'mcp'` or enrich an existing logical `tool_call_id`. An MCP call observed without a corresponding logical tool call is an integrity/security finding, not a separate product counter. Key cardinalities: - One session has many `trace_id` values. - One `trace_id` has one or more `turn_id` values. - One `turn_id` has one or more `model_call_id` values. - One `model_call_id` has one provider request and one provider response. - One `model_call_id` has many `model_items` rows: request, reasoning, response, tool_call, and tool_response items in observed order. - One `model_call_id` can emit many `tool_call_id` values. - One `tool_call_id` has one tool request and zero or more observed response rows, all with the same `tool_call_id`. - One `event_id` identifies one emitted row and joins its security, body, and display details. ### net_events -- one row per HTTP request through MITM proxy ```sql CREATE TABLE net_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT NOT NULL, -- RFC 3339 domain TEXT NOT NULL, -- "api.anthropic.com" port INTEGER DEFAULT 443, decision TEXT NOT NULL, -- "allowed" or "denied" process_name TEXT, -- "claude", "node", "python3" pid INTEGER, method TEXT, -- "POST", "GET" path TEXT, -- "/v1/messages" query TEXT, -- URL query string status_code INTEGER, -- 200, 403, etc. bytes_sent INTEGER DEFAULT 0, bytes_received INTEGER DEFAULT 0, duration_ms INTEGER DEFAULT 0, matched_rule TEXT, -- which policy rule matched request_headers TEXT, -- JSON (allowlisted verbatim, others hashed) response_headers TEXT, request_body_preview TEXT, -- compact display field only response_body_preview TEXT, conn_type TEXT DEFAULT 'https' ); ``` ### model_calls -- one row per AI API request+response cycle ```sql CREATE TABLE model_calls ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT NOT NULL, provider TEXT NOT NULL, -- "anthropic", "openai", "google" model TEXT, -- "claude-sonnet-4-20250514", "gpt-4o" process_name TEXT, pid INTEGER, method TEXT NOT NULL, -- "POST" path TEXT NOT NULL, -- "/v1/messages" stream INTEGER DEFAULT 0, -- 1 if SSE streaming system_prompt_preview TEXT, messages_count INTEGER DEFAULT 0, tools_count INTEGER DEFAULT 0, request_bytes INTEGER DEFAULT 0, request_body_preview TEXT, -- compact display field only message_id TEXT, -- "msg_..." (Anthropic), "chatcmpl-..." (OpenAI) status_code INTEGER, text_content TEXT, -- full response text thinking_content TEXT, -- thinking/reasoning text stop_reason TEXT, -- "end_turn", "tool_use", "stop", "STOP" input_tokens INTEGER, output_tokens INTEGER, duration_ms INTEGER DEFAULT 0, response_bytes INTEGER DEFAULT 0, estimated_cost_usd REAL DEFAULT 0, trace_id TEXT, -- groups tool call chains across turns usage_details TEXT -- JSON: {"cache_read": N, "thinking": N} ); ``` Only emitted for actual LLM API paths (`/v1/messages`, `/v1/chat/completions`, `/v1beta/models/*/`). Health checks, auth endpoints don't create rows. ### tool_calls -- canonical tool invocation ledger ```sql CREATE TABLE tool_calls ( id INTEGER PRIMARY KEY AUTOINCREMENT, event_id TEXT NOT NULL, -- 12 hex chars timestamp TEXT NOT NULL, model_call_id INTEGER, -- model_calls.id that emitted the tool call when model-visible provider TEXT NOT NULL, status TEXT NOT NULL, -- "requested", "observed", "responded", "error" call_index INTEGER NOT NULL, -- position in response call_id TEXT NOT NULL, -- "toolu_..." (Anthropic), "call_..." (OpenAI) tool_name TEXT NOT NULL, arguments TEXT, -- JSON string response_preview TEXT, origin TEXT NOT NULL DEFAULT 'native', -- "native", "mcp", "builtin", or "local" server_name TEXT, method TEXT, request_id TEXT, decision TEXT NOT NULL, duration_ms INTEGER DEFAULT 0, error_message TEXT, process_name TEXT, bytes_sent INTEGER DEFAULT 0, bytes_received INTEGER DEFAULT 0, policy_mode TEXT, policy_action TEXT, policy_rule TEXT, policy_reason TEXT, trace_id TEXT, credential_ref TEXT ); ``` For model-emitted tool calls, `model_call_id` points to the model exchange whose response emitted that tool call. It is not a trace-level guess. ### tool_responses -- results sent back for tool calls ```sql CREATE TABLE tool_responses ( id INTEGER PRIMARY KEY AUTOINCREMENT, model_call_id INTEGER NOT NULL, -- model_calls.id whose request consumed the tool result call_id TEXT NOT NULL, -- matches tool_calls.call_id content_preview TEXT, is_error INTEGER DEFAULT 0, trace_id TEXT, credential_ref TEXT ); ``` `tool_responses.model_call_id` points to the later model exchange that carried the tool result back to the model. The same `call_id` must match a `tool_calls.call_id` in the same trace. MCP initialize/list/resource protocol evidence is in the forensic payload of the matching `security_rule_events` row. That payload is archive-backed, not a column: read it with `DbHandle::read_body(event_id, "security_rule_events", BodyDirection::Payload)`, or join `event_body_blobs` on `source_table = 'security_rule_events'` and `direction = 'payload'` to see what is stored. Decisions and asks archive the same way under `security_decision_events` and `security_ask_events`, and all three name the same event, so a payload read always names its table. Use `tool_calls` for product/user/security tool activity.
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen