Skip to content

Session Telemetry

Every Capsem VM gets its own SQLite database (session.db) that records network requests, DNS queries, AI model calls, MCP tool invocations, exec activity, kernel audit events, file changes, security rule matches, credential substitutions, and snapshots. The database lives in the session directory and follows the VM lifecycle; retained/forked VMs keep their database for forensic review.

The route/session id is an opaque VM id and is the only key that may select a session directory, session.db, active instance, DB handle, terminal/log/stats route, or UI tab. A user-facing VM name such as co-work1 or code-vm1 is a display/resume alias only.

API payloads that describe persistent sessions must keep both fields: id for routing and DB lookup, name for display. User surfaces may accept commands like capsem resume co-work1, but that layer must translate the name to the VM id before calling /vms/{id}/.... Service route code must not collapse name into id; doing so can make a selected session read another session’s database and show the wrong provider/model data.

erDiagram
    net_events {
        int id PK
        text event_id
        text domain
        text decision
        text method
        text path
        int status_code
        int bytes_sent
        int bytes_received
        int duration_ms
    }
    model_calls {
        int id PK
        text event_id
        text provider
        text model
        int input_tokens
        int output_tokens
        real estimated_cost_usd
        text trace_id
        text turn_id
    }
    model_items {
        int id PK
        int model_call_id FK
        text item_type
        int item_index
        text call_id
        text trace_id
        text turn_id
    }
    tool_calls {
        int id PK
        text event_id
        int model_call_id FK
        text call_id
        text tool_name
        text origin
        text trace_id
        text turn_id
    }
    tool_responses {
        int id PK
        int model_call_id FK
        text call_id
        text content_preview
        text trace_id
        text turn_id
    }
    dns_events {
        int id PK
        text event_id
        text qname
        int qtype
        int rcode
        text decision
    }
    event_body_blobs {
        int id PK
        text event_id
        text event_type
        text source_table
        text direction
        text body_hash
        blob body
    }
    security_rule_events {
        int id PK
        text event_id
        text event_type
        text rule_id
        text rule_action
        text detection_level
        text rule_json
        text event_json
    }
    security_ask_events {
        int id PK
        text ask_id
        text event_id
        text event_type
        text rule_id
        text status
        text rule_json
        text event_json
    }
    exec_events {
        int id PK
        int exec_id
        text command
        int exit_code
        int duration_ms
    }
    audit_events {
        int id PK
        int pid
        int ppid
        text exe
        text argv
    }
    fs_events {
        int id PK
        text action
        text path
        int size
    }
    model_calls ||--o{ model_items : "orders"
    model_calls ||--o{ tool_calls : "emits"
    tool_calls ||--o{ tool_responses : "same call_id"
    model_calls ||--o{ tool_responses : "consumes"
    net_events ||--o{ security_rule_events : "event_id"
    net_events ||--o{ event_body_blobs : "event_id"
    model_calls ||--o{ event_body_blobs : "event_id"
    tool_calls ||--o{ event_body_blobs : "event_id"
    dns_events ||--o{ security_rule_events : "event_id"
    security_rule_events ||--o{ security_ask_events : "event_id"

The ledger uses a small set of scoped ids. Each id has one job, and no provider id replaces these joins:

  • 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.

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/>full request/response bodies by event_id"]
    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

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. In the current SQLite schema, the persisted tool_call_id value is stored in tool_calls.call_id and tool_responses.call_id.

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.

The key cardinalities are:

  • 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.

Every HTTP request through the MITM proxy, whether allowed or denied.

ColumnTypeDescription
idINTEGER PKAuto-increment
event_idTEXT12-hex primary event id for security_rule_events joins
timestampTEXTISO 8601
domainTEXTTarget domain
portINTEGERDefault 443
decisionTEXTallowed, denied, error
process_nameTEXTGuest process that initiated the request
pidINTEGERGuest process ID
methodTEXTHTTP method
pathTEXTRequest path
queryTEXTQuery string
status_codeINTEGERUpstream response status
bytes_sentINTEGERRequest body size
bytes_receivedINTEGERResponse body size
duration_msINTEGEREnd-to-end latency
matched_ruleTEXTCompatibility helper; security rule truth is in security_rule_events
request_headersTEXTRequest headers (when body logging enabled)
response_headersTEXTResponse headers
request_body_previewTEXTCompact display field; forensic body truth is in event_body_blobs
response_body_previewTEXTCompact display field; forensic body truth is in event_body_blobs
conn_typeTEXTDefault https, https-mitm for proxied
policy_modeTEXTTransport-local policy mode hint, when set
policy_actionTEXTDenormalized transport hint; security_rule_events.rule_action is rule truth
policy_ruleTEXTDenormalized transport hint; security_rule_events.rule_id is rule truth
policy_reasonTEXTDenormalized transport hint; security_rule_events.rule_json is rule truth
trace_idTEXTCross-table correlation ID

AI provider API calls with parsed response metadata.

ColumnTypeDescription
idINTEGER PKAuto-increment
event_idTEXT12-hex primary event id for security_rule_events joins
timestampTEXTISO 8601
providerTEXTanthropic, openai, google
modelTEXTe.g. claude-opus-4
process_nameTEXTGuest process
pidINTEGERGuest process ID
methodTEXTHTTP method (always POST)
pathTEXTAPI path (e.g. /v1/messages)
streamINTEGERBoolean: 1 if SSE streaming
system_prompt_previewTEXTFirst N chars of system prompt
messages_countINTEGERNumber of messages in request
tools_countINTEGERNumber of tools in request
request_bytesINTEGERRequest body size
request_body_previewTEXTCompact display field; forensic body truth is in event_body_blobs
message_idTEXTProvider message ID
status_codeINTEGERHTTP status
text_contentTEXTConcatenated text output
thinking_contentTEXTChain-of-thought output
stop_reasonTEXTend_turn, tool_use, max_tokens, content_filter
input_tokensINTEGERInput token count
output_tokensINTEGEROutput token count
duration_msINTEGERRequest duration
response_bytesINTEGERResponse body size
estimated_cost_usdREALCost estimate from pricing table
trace_idTEXTLinks multi-turn agent conversations
turn_idTEXTUser-visible agent turn that contains this model exchange
usage_detailsTEXTJSON: {"cache_read": 800, "thinking": 200}

Full captured request and response bodies for HTTP, model, and tool events. The primary protocol tables keep compact display fields for table scans; forensic body truth lives here and joins by event_id plus direction.

ColumnTypeDescription
idINTEGER PKAuto-increment
event_idTEXT12-hex event id from net_events, model_calls, or tool_calls
event_typeTEXTCanonical event type such as http.request, model.call, or mcp.tool_call
source_tableTEXTnet_events, model_calls, or tool_calls
directionTEXTrequest or response
content_typeTEXTMIME type or protocol content type, when known
original_bytesINTEGERFull body byte count observed at the boundary
stored_bytesINTEGERBytes persisted in body
truncatedINTEGER1 when the persisted body hit the capture limit
body_hashTEXTblake3:* hash of the observed body bytes
bodyBLOBCaptured body bytes, currently bounded to 10 MB per direction
trace_idTEXTCross-table correlation ID
created_atTEXTInsert timestamp

The UI and debug routes may render parsed JSON, text, or binary summaries from this table, but they must not invent a second body source. If a compact preview and a blob disagree, the blob table is the ledger.

Canonical product/security tool invocation ledger. One row per model-native, built-in/local, or MCP-origin tool invocation. User-facing tool counts, CEL tool evidence, and forensic tool activity start here.

ColumnTypeDescription
idINTEGER PKAuto-increment
model_call_idINTEGER FKReferences model_calls.id
call_indexINTEGERPosition in the response
call_idTEXTCanonical tool_call_id value for this logical tool invocation
tool_nameTEXTTool name
argumentsTEXTJSON arguments
originTEXTnative, mcp, builtin, local, or mcp_proxy
trace_idTEXTCross-table correlation ID
turn_idTEXTUser-visible agent turn that contains this tool invocation

Tool results from subsequent requests (matched by call_id).

ColumnTypeDescription
idINTEGER PKAuto-increment
model_call_idINTEGER FKReferences model_calls.id
call_idTEXTSame canonical tool_call_id value as tool_calls.call_id
content_previewTEXTTruncated tool result
is_errorINTEGERBoolean: 1 if tool returned error
trace_idTEXTCross-table correlation ID
turn_idTEXTUser-visible agent turn that contains this tool response

DNS queries handled by the host DNS proxy.

ColumnTypeDescription
idINTEGER PKAuto-increment
event_idTEXT12-hex primary event id for security_rule_events joins
timestampTEXTISO 8601
qnameTEXTQueried name
qtypeINTEGERDNS record type
qclassINTEGERDNS class
rcodeINTEGERDNS response code
decisionTEXTallowed, denied, redirected, or error
matched_ruleTEXTCompatibility helper; security rule truth is in security_rule_events
source_protoTEXTDNS transport source
process_nameTEXTGuest process, when known
upstream_resolver_msINTEGERUpstream resolver latency
trace_idTEXTCross-table correlation ID
policy_modeTEXTTransport-local policy mode hint, when set
policy_actionTEXTDenormalized transport hint; security_rule_events.rule_action is rule truth
policy_ruleTEXTDenormalized transport hint; security_rule_events.rule_id is rule truth
policy_reasonTEXTDenormalized transport hint; security_rule_events.rule_json is rule truth

Every matched security rule, across HTTP, DNS, MCP, model, file, and process events. Credential substitution and snapshot lifecycle rows may appear in the ledger, but 1.3 does not expose fake credential.* or snapshot.* rule roots.

ColumnTypeDescription
idINTEGER PKAuto-increment
timestamp_unix_msINTEGERMatch timestamp
event_idTEXT12-hex primary event id from the protocol/event table
event_typeTEXTCanonical security event type such as http.request, mcp.tool_call, or file.read
rule_idTEXTStable rule id such as profiles.rules.skill_loaded
rule_actionTEXTallow, ask, block, preprocess, rewrite, or postprocess
detection_levelTEXTnone, informational, low, medium, high, or critical
rule_jsonTEXTJSON rule snapshot at match time
event_jsonTEXTJSON normalized SecurityEvent payload matched by the rule
trace_idTEXTCross-table correlation ID

This table is the forensic rule ledger. Runtime /latest and /status views must be regeneratable from these rows and the primary event tables.

Append-only lifecycle rows for ask decisions.

ColumnTypeDescription
idINTEGER PKAuto-increment
timestamp_unix_msINTEGERAsk lifecycle timestamp
ask_idTEXT12-hex ask id
event_idTEXT12-hex primary event id
event_typeTEXTCanonical security event type
rule_idTEXTRule that requested ask
rule_nameTEXTRule telemetry name
statusTEXTpending, approved, or denied
rule_jsonTEXTJSON rule snapshot
event_jsonTEXTJSON normalized SecurityEvent payload
resolverTEXTApprover/resolver identity, when present
reasonTEXTResolution reason, when present
trace_idTEXTCross-table correlation ID

Commands executed through Capsem service APIs and MCP tools.

ColumnTypeDescription
idINTEGER PKAuto-increment
event_idTEXT12-hex primary event id for ledger joins
timestampTEXTISO 8601
exec_idINTEGERPer-session exec identifier
commandTEXTCommand string
exit_codeINTEGERProcess exit code, when complete
duration_msINTEGERRuntime duration, when complete
stdout_previewTEXTTruncated stdout
stderr_previewTEXTTruncated stderr
stdout_bytesINTEGERFull stdout byte count
stderr_bytesINTEGERFull stderr byte count
sourceTEXTSource path, usually api or MCP
trace_idTEXTCross-table correlation ID
process_nameTEXTGuest process name, when known
pidINTEGERGuest process ID, when known
credential_refTEXTBrokered credential reference, when present

Kernel audit execve records streamed from the guest over vsock:5006.

ColumnTypeDescription
idINTEGER PKAuto-increment
timestampTEXTISO 8601
pidINTEGERGuest process ID
ppidINTEGERGuest parent process ID
uidINTEGERGuest user ID
exeTEXTExecutable path
commTEXTKernel command name
argvTEXTReconstructed command arguments
cwdTEXTWorking directory
exit_codeINTEGERExit code, when known
session_idINTEGERKernel audit session ID
ttyTEXTTTY, when present
audit_idTEXTKernel audit event ID
exec_event_idINTEGERRelated exec_events.id, when correlated
parent_exeTEXTParent executable, when known
trace_idTEXTCross-table correlation ID

File system changes in the workspace (tracked by VirtioFS).

ColumnTypeDescription
idINTEGER PKAuto-increment
event_idTEXT12-hex primary event id for ledger joins
timestampTEXTISO 8601
actionTEXTcreated, modified, deleted, restored
pathTEXTFile path relative to workspace
sizeINTEGERFile size in bytes
trace_idTEXTCross-table correlation ID
credential_refTEXTBrokered credential reference, when present

Automatic and manual workspace snapshot state is not a session DB table. Snapshots are host recovery state, exposed through VM-scoped snapshot routes. Running VMs answer from the capsem-process in-memory scheduler over IPC; stopped VMs reconstruct status from that VM’s snapshot metadata only when a snapshot route is requested. Explicit snapshot MCP calls remain visible as MCP activity, and file restores remain visible as fs_events.

graph LR
    subgraph "Event Sources"
        MITM["MITM Proxy<br/>(vsock:5002)"]
        MCP["MITM MCP Endpoint<br/>(framed vsock:5002)"]
        DNS["DNS Proxy"]
        EXEC["Service exec path"]
        AUDIT["Guest audit stream<br/>(vsock:5006)"]
        FS["VirtioFS<br/>(file watcher)"]
        SNAP["Snapshot scheduler"]
        SNAPAPI["VM snapshot routes<br/>/vms/{id}/snapshots/*"]
    end

    subgraph "Writer Pipeline"
        CH["tokio mpsc channel"]
        WT["Dedicated writer thread<br/>(capsem-db-writer)"]
        DB["session.db<br/>(SQLite WAL)"]
    end

    MITM -->|"WriteOp::NetEvent<br/>WriteOp::ModelCall"| CH
    MCP -->|"WriteOp::McpCall"| CH
    DNS -->|"WriteOp::DnsEvent"| CH
    EXEC -->|"WriteOp::ExecEvent<br/>WriteOp::ExecEventComplete"| CH
    AUDIT -->|"WriteOp::AuditEvent"| CH
    FS -->|"WriteOp::FileEvent"| CH
    SNAP -->|"in-memory IPC status"| SNAPAPI
    CH --> WT
    WT --> DB
VariantSourceTable(s)
WriteOp::NetEventMITM proxynet_events
WriteOp::ModelCallMITM proxy (AI traffic)model_calls + tool_calls + tool_responses
WriteOp::McpCallMITM MCP endpointtool_calls for tools/call; security_rule_events for protocol evidence
WriteOp::ExecEvent / ExecEventCompleteService exec pathexec_events
WriteOp::AuditEventGuest audit streamaudit_events
WriteOp::FileEventVirtioFS watcherfs_events
WriteOp::DnsEventDNS proxydns_events
WriteOp::SecurityRuleEventSecurity enginesecurity_rule_events
WriteOp::SecurityAskEventSecurity enginesecurity_ask_events

Use just query-session to prove that a security rule matched, which primary event it matched, and which normalized payload the rule saw. The ledger is security_rule_events; protocol tables provide the boundary-specific details.

Terminal window
just query-session "
SELECT event_id, event_type, rule_id, rule_action, detection_level, trace_id
FROM security_rule_events
ORDER BY timestamp_unix_ms DESC
LIMIT 20;"

For forensic review, inspect the stored rule and event snapshots:

Terminal window
just query-session "
SELECT rule_id, rule_json, event_json
FROM security_rule_events
WHERE event_id = '<event_id>'
ORDER BY id DESC;"
Terminal window
just query-session "
SELECT n.event_id, n.domain, n.method, n.path, n.decision,
s.rule_id, s.rule_action, s.detection_level
FROM net_events n
JOIN security_rule_events s ON s.event_id = n.event_id
ORDER BY n.id DESC
LIMIT 20;"
Terminal window
just query-session "
SELECT d.event_id, d.qname, d.qtype, d.rcode, d.decision,
s.rule_id, s.rule_action, s.detection_level
FROM dns_events d
JOIN security_rule_events s ON s.event_id = d.event_id
ORDER BY d.id DESC
LIMIT 20;"
Terminal window
just query-session "
SELECT t.event_id, t.server_name, t.method, t.tool_name, t.decision,
s.rule_id, s.rule_action, s.detection_level, t.error_message
FROM tool_calls t
LEFT JOIN security_rule_events s ON s.event_id = t.event_id
WHERE t.origin = 'mcp'
ORDER BY t.id DESC
LIMIT 20;"
Terminal window
just query-session "
SELECT ask_id, event_id, rule_id, rule_name, status, resolver, reason
FROM security_ask_events
ORDER BY timestamp_unix_ms DESC
LIMIT 20;"

For no-dispatch checks, pair an ask or block rule row with the primary event row and the expected boundary result. The rule decision is security_rule_events.rule_action; the primary table’s decision remains the transport outcome at that boundary.

The DbWriter spawns a dedicated thread that owns the SQLite connection:

  1. Async callers send WriteOp via tx.send() (non-blocking)
  2. Writer thread blocks on rx.blocking_recv() for the first op
  3. After receiving one op, drains the rest of the queue
  4. Executes all drained ops in a single SQLite transaction
  5. Repeats

This block-then-drain pattern batches writes for efficiency while keeping the async callers non-blocking. The channel has configurable backpressure capacity.

SQLite pragmas: WAL journal mode, NORMAL synchronous. Field values are defensively capped at 256 KB.

Drop order is critical: Drop::drop() takes tx before joining the thread. Without this, the join would deadlock (thread waits for all senders to drop, but tx drops after the join).

graph TD
    A["MITM proxy receives<br/>AI provider response"] --> B["AiResponseBody wraps<br/>hyper Body"]
    B --> C["poll_frame() feeds bytes<br/>to SseParser"]
    C --> D["SseParser emits SseEvent"]
    D --> E["ProviderStreamParser<br/>(Anthropic/OpenAI/Google)"]
    E --> F["Vec&lt;LlmEvent&gt;"]
    F --> G["collect_summary()"]
    G --> H["StreamSummary<br/>(text, tools, tokens, cost)"]
    H --> I["TelemetryEmitter.emit_model_call()"]
    I --> J["WriteOp::ModelCall<br/>with ToolCallEntry + ToolResponseEntry"]

For AI provider traffic, the response body is parsed inline to extract:

  • Model name and message ID
  • Text and thinking output
  • Tool calls with arguments and origin classification
  • Token usage (input, output, cache_read, thinking breakdowns)
  • Cost estimate from embedded pricing table
  • Stop reason (end_turn, tool_use, max_tokens)
  • Trace ID for multi-turn correlation

The DbReader provides pre-built aggregate queries:

QueryReturnsUse case
session_stats()SessionStatsDashboard summary: totals for net, model, tokens, cost
provider_token_usage()Vec<ProviderTokenUsage>Per-provider breakdown: call count, tokens, cost
domain_counts()Vec<DomainCount>Per-domain request counts with allowed/denied split
time_buckets()Vec<TimeBucket>Requests over time (for charts)
tool_usage()Vec<ToolUsageCount>Most-used tools by call count
tool_usage_with_stats()Vec<ToolUsageWithStats>Tool usage with byte and duration stats
mcp_tool_usage()Vec<McpToolUsage>MCP tool usage by server and tool name
trace_summaries()Vec<TraceSummary>Per-trace: tokens, cost, duration, tool count
trace_detail(id)TraceDetailAll model calls in a trace with tool data
Access pointProtocolQuery type
capsem info <id> --statsCLI -> service HTTP /vms/{id}/infoPre-built SessionStats
Frontend Stats tabGateway -> typed VM-scoped ledger routesPer-table summaries and event inspection
MCP capsem_timelineMCP -> service HTTP /vms/{id}/timelineTyped time-ordered event stream
MCP logs/triage toolsMCP -> typed service routesLogs, panic triage, and operational diagnostics

Capsem does not expose arbitrary SQL over HTTP, gateway, frontend, or MCP. session.db is the durable ledger and can be inspected directly by a developer when doing local forensics, but product routes use typed logger/database APIs. Any hot mem/disk split belongs inside the logger DB object, never in service route state.

The VM Stats tab is ledger/database backed. It does not infer security state from profile config or live rules. It reads typed service ledger routes that are backed by the logger DB API and VM-scoped rule routes:

Stats tabPrimary source
Modelmodel_calls
Toolstool_calls
HTTPnet_events
DNSdns_events
Filesfs_events
Processexec_events, audit_events
Credentialssubstitution_events
Security/vms/{id}/security/latest, /vms/{id}/security/status, /vms/{id}/detection/latest, /vms/{id}/enforcement/latest
Snapshots/vms/{id}/snapshots/status, /vms/{id}/snapshots/list

The old raw SQL Inspector tab and /vms/{id}/inspect route were removed. Add new typed logger DB APIs when the UI, TUI, MCP, or CLI needs more ledger evidence; do not reintroduce a general SQL proxy or service-owned logged-data projection.

PropertyValue
Location~/.capsem/sessions/{id}/session.db
LifetimeCreated at VM boot and retained or deleted with the VM’s lifecycle state
AccessOnly the owning capsem-process can write; service reads via IPC
VirtioFS boundarysession.db is outside the VirtioFS share; guest cannot access it
Concurrent accessWAL mode allows concurrent reader + writer
Fork behaviorcapsem fork checkpoints and copies session.db into the image
FilePurpose
capsem-logger/src/schema.rsTable DDL, pragmas, migrations
capsem-logger/src/events.rsEvent structs (NetEvent, ModelCall, McpCall, etc.)
capsem-logger/src/writer.rsDbWriter, WriteOp, block-then-drain loop
capsem-logger/src/reader.rsDbReader, aggregation queries, raw SQL
capsem-logger/src/db.rsSessionDb convenience wrapper