AcTrail Use Cases

This document maps common operator questions to the AcTrail feature path and the example that proves it.

Use Case Matrix

Question Use This Path Validate With
What process tree did the agent create? eBPF process lifecycle plus viewer process/events output. Example 01, Example 03
Did one agent silently launch another agent? actrailctl launch plus process seccomp exec context, TLS/plaintext LLM evidence, and agent.invocation semantic action. Example 07
What LLM request and response did a real agent exchange? Full-monitor payload capture plus HTTP/SSE and llm.request/llm.response semantic assembly. Example 08
What LLM request and response did opencode exchange? Bun/static-BoringSSL executable TLS payload capture plus proxy-aware HTTP semantics. python3 tests/agent-trace/run_case.py opencode-bun
What outbound LLM request did a Rust/rustls agent send? tls-sync with a rustls probe plan from finder fast, symbols, or a build-id-checked pattern. Example 06
What outbound LLM request did a LangGraph Python agent send? eBPF dynamic OpenSSL shared-library TLS payload capture around an OpenAI-compatible HTTPS call. python3 tests/agent-trace/run_case.py langgraph-openai
What HTTP request/response did a non-TLS local service exchange? Socket plaintext payload with HTTP sniffing and HTTP/1.x application analyzer. Example 05
Did file enforcement actually block the target? Fanotify permission backend plus client-side stdout and Enforcement events. Example 04
What files, IPC, stdio, and resources did a workload touch? Extended eBPF observation, stdio payload, provider labels, resource metrics. Example 03
Can I export higher-level actions to observability tooling? actrailviewer export-otel over semantic actions. Example 04, Example 08
What is the runtime overhead? tests/performance/run_benchmark.py task-runtime benchmark. Performance README

Agent Invocation Discovery

Goal: detect a tree-shaped call such as one process launching claude -p ..., where Claude is proven to be an agent by an outbound LLM request.

Use:

python3 docs/examples/07.xiaoo-claude-agent-invocation/run_e2e.py

Expected evidence:

  • The monitored workload prints ACTRAIL_AGENT_TREE_OK.
  • OTEL export contains a process.exec span for claude -p.
  • OTEL export contains an llm.request span for the same Claude process.
  • OTEL export contains an agent.invocation span linking Claude's direct launcher to the child Claude command.

This use case requires payload capture for the child LLM call. A command match is only a candidate hint; it does not prove agent identity by itself.

Complete LLM Exchange Capture

Goal: retain the LLM request and response bytes, derive an llm.request action from the outbound request, and derive an llm.response action from inbound JSON or SSE response payloads.

For manual real-agent validation, start with Example 08. Use a runtime-specific capture path when narrowing a failure:

Target Runtime Capture Path
Dynamic OpenSSL payload_tls_source = shared-library, payload_tls_resolver = openssl-symbols; use eBPF for Python _ssl because libssl is loaded after process startup.
Node/OpenSSL executable tls-sync auto plan via payload_tls_source = auto, payload_tls_resolver = auto, and payload_tls_library = auto.
Bun/static-BoringSSL tls-sync auto plan via finder fast; BoringSSL-specific static detection is an implementation detail, not an operator config value in current examples.
Rust/rustls tls-sync auto plan with finder fast resolving rustls plaintext points from symbols, debuginfo, or supported build-id-checked patterns.
Go crypto/tls payload_tls_source = executable, payload_tls_resolver = go-pclntab, payload_tls_library = go, payload_tls_capture_backend = bpf-copy-seccomp-fallback; captures crypto/tls.(*Conn).Write outbound and crypto/tls.(*Conn).Read inbound through the Go runtime copy path.
Plain HTTP endpoint/proxy payload_socket_enabled = true, payload_socket_capture_backend = bpf-copy-seccomp-fallback, plus HTTP/1.x application parsing.

Real agent acceptance cases:

Case Command Runtime Boundary
Claude Code python3 tests/agent-trace/run_case.py claude-code HTTPS/TLS when supported, otherwise plain HTTP socket plaintext if configured by provider/proxy.
opencode python3 tests/agent-trace/run_case.py opencode-bun Bun/static-BoringSSL TLS writes or plain HTTP socket plaintext; model pinned to deepseek/deepseek-chat.
xiaoO python3 tests/agent-trace/run_case.py xiaoo-rustls Rust/rustls plaintext points or plain HTTP socket plaintext. Stripped HTTPS/HTTP CONNECT binaries need a supported finder rustls plan before this payload case can pass.
LangGraph LANGGRAPH_PYTHON=/path/to/python python3 tests/agent-trace/run_case.py langgraph-openai Python dynamic OpenSSL shared-library writes for HTTPS via eBPF; socket plaintext for HTTP API URLs.

On hosts that require a local proxy for external network access, keep the proxy environment active for these commands. Do not assume the provider route is HTTPS: some agent configs use a plain HTTP base URL or proxy. When HTTPS is used, proxy tunnel facts such as CONNECT api.deepseek.com:443 may appear alongside decrypted TLS request rows. When plain HTTP is used, the request payload source is Syscall/socket-syscall.

For large LLM requests, set the relevant per-operation budget high enough before running the case. Rust/rustls TLS examples use tls-sync, so the runtime reports each TLS operation to the daemon instead of forcing every payload into a fixed multi-MB eBPF ringbuf record. Python OpenSSL uses eBPF shared-library probes because _ssl maps libssl lazily. Go crypto/tls capture is entry direct-copy and therefore only emits operations that fit the configured TLS segment budget. Plain HTTP socket payload can still use bpf-copy-seccomp-fallback: eBPF copies operations no larger than the inline budget, while seccomp notification is the user-space read fallback for larger socket operations.

Run TLS capture through actrailctl launch; do not use track-add for tls-sync, because the sync runtime, event socket, and probe plan must be prepared before exec.

Expected evidence:

  • actrailviewer payloads shows complete outbound plaintext rows: TlsUserSpace for HTTPS/TLS or Syscall/socket-syscall for plain HTTP.
  • Operation state is success.
  • Captured size equals original size for the operation being validated.
  • actrailviewer actions contains a complete successful llm.request and an llm.response when inbound JSON/SSE response payload is retained.
  • actrailviewer export-json contains payload nodes if payload export is enabled.
  • actrailviewer export-otel contains llm.request and the assembled llm.response span when that action exists.

Rows marked Truncated or operation state partial mean the configured capture budget is not sufficient for that request.

HTTP Semantics

Goal: inspect application protocol facts instead of only raw bytes.

Use:

Expected evidence:

  • Payload rows show the plaintext source boundary: Syscall for plain HTTP, TlsUserSpace for HTTPS.
  • Application events show HTTP request/response, HTTP/2 frames, or HTTP/2 DATA facts depending on the protocol.
  • llm.request actions are assembled from retained outbound request payloads when the payload is complete.
  • llm.response actions are assembled from retained inbound JSON or SSE payloads; multiple SSE chunks on the same stream update one response action with multiple payload-segment evidence rows.

HTTP parsing consumes retained plaintext payloads. It should not depend on whether bytes came from OpenSSL, BoringSSL, rustls, or socket syscalls.

File Enforcement

Goal: prove the observed system both enforced a policy and recorded the decision.

Use:

python3 docs/examples/04.fanotify-enforcement-e2e/run_e2e.py \
  --otel-output /tmp/actrail-fanotify-e2e.otlp.json

Expected evidence:

  • Target-side stdout includes allowed=ok.
  • Target-side stdout includes denied=permission_denied.
  • Viewer output contains one allow and one deny Enforcement event.
  • OTEL export contains two enforcement.decision spans when --otel-output is used.

If the target prints denied=unexpected_success, the policy did not block access even if AcTrail produced events.

Broad Local Observation

Goal: validate process, file, IPC, network, stdio, provider, and resource observation on one host.

Use:

python3 docs/examples/clean.py --example extended-observation
./target/release/ebpf_probe verify-live \
  --config docs/examples/03.extended-observation-e2e/observation.conf

Expected evidence includes:

process_events=exec,exit,fork
file_events=...
net_events=...
ipc_events=...
resource_events=process_tree
stdio_payloads=stderr:outbound,stdin:inbound,stdout:outbound

The fork event comes from sched/sched_process_fork; a host does not need syscalls/sys_enter_fork for this path. Default process lifecycle capture suppresses process signal events such as SIGCHLD.

OpenTelemetry Export

Goal: feed higher-level AcTrail actions into an observability pipeline.

Use:

./target/release/actrailviewer export-otel \
  --config <operator.conf> \
  --trace-id <TRACE_ID> \
  --output /tmp/actrail-trace.otlp.json

Expected evidence:

  • Top-level JSON contains resourceSpans.
  • Semantic spans contain actrail.action.kind.
  • Action kinds currently covered by examples include process.exec, agent.invocation, file.read, file.write, http.message, llm.request, llm.response, and enforcement.decision.

Not every raw fact is currently an OTEL span. Validate low-level network rows, IPC rows, resource samples, provider labels, and payload rows through viewer commands or JSON graph export when no semantic span exists.

Performance Measurement

Goal: measure task runtime overhead, not one-time wrapper setup time.

Use:

python3 tests/performance/run_benchmark.py \
  --case agent \
  --mode baseline,observed-ebpf-core,observed-ebpf-payload,observed-seccomp-agent \
  --repetitions 10 \
  --output local/performance-agent.md

Read task_runtime_ms for the primary overhead question. outer_wall_ms is reported separately to show runner and actrailctl launch control-plane cost. The report uses distribution-level evidence rather than treating same-numbered iterations as paired samples.