This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
AgentSight is an eBPF-based observability framework for monitoring AI agent behavior through SSL/TLS traffic interception and process monitoring. It captures unencrypted request/response data at the kernel level without requiring any code changes to target applications.
# Full build (eBPF + Rust collector + frontend)
make build
# Individual components
make build-bpf # eBPF C programs only
cd collector && cargo build --release # Rust collector only
cd frontend && npm install && npm run build # Frontend only
# Tests
cd bpf && make test # 60 C unit tests
cd collector && cargo test # 89+ Rust tests
cd frontend && npm run lint # Frontend linting
# Run a single Rust test
cd collector && cargo test test_name
# Debug builds with AddressSanitizer
cd bpf && make debug
cd bpf && make sslsniff-debug
# Install system dependencies (Ubuntu/Debian)
make install# Live agent sessions
sudo ./agentsight top
# Launch and record a command
sudo ./agentsight record -- claude
# Record Claude Code (requires --binary-path for statically-linked BoringSSL)
sudo ./agentsight record -c claude --binary-path ~/.local/share/claude/versions/<version>
# Record Python AI tools
sudo ./agentsight record -c python
# Record with NVM Node.js (statically-linked OpenSSL)
sudo ./agentsight record -c node --binary-path ~/.nvm/versions/node/v20.0.0/bin/node
# Direct eBPF program usage
sudo ./bpf/sslsniff --binary-path <path>
sudo ./bpf/process -c python
# Web UI available at http://127.0.0.1:7395 when using record/stat live capture.
# debug trace needs --server.eBPF Programs (kernel) → JSON stdout → Rust Runners → Analyzer Chain → Output/Frontend/Files
bpf/— C eBPF programs.sslsniffhooks SSL_read/SSL_write via uprobes;processtracks process lifecycle via tracepoints. Both emit JSON to stdout.collector/src/framework/— Rust streaming framework:runners/— Execute eBPF binaries and parse their JSON output into event streams (SslRunner, ProcessRunner, SystemRunner, FakeRunner)analyzers/— Pluggable stream processors: SSEProcessor, HTTPParser, SSLFilter, HTTPFilter, AuthHeaderRemover, TimestampNormalizer, OtelExporter (maps LLM HTTP pairs to OpenTelemetrygen_ai.*spans, exported via OTLP/HTTP JSON; enabled bydebug trace --otel, seedocs/otel.md)core/events.rs— StandardizedEventstruct with JSON payloadsbinary_extractor.rs— Extracts embedded eBPF binaries to temp files at runtime
collector/src/main.rs— CLI entry point. Main subcommands:stat,top,record,report(summary,token,audit,prompts,export,list),discover, anddebug(ssl,process,stdio,trace,system).collector/src/server/— Hyper-based embedded web server serving frontend assets and/api/eventsfrontend/— Next.js/React/TypeScript visualization with timeline, process tree, and log views
Runners use a fluent builder pattern: SslRunner::new().with_args(&args).add_analyzer(Box::new(HTTPParser::new())).run().await
Each Runner produces an EventStream (async Stream of Events). Analyzers transform streams in sequence. The AgentRunner orchestrates multiple runners concurrently via RunnerOrchestrator.
All timestamps are nanoseconds since boot (bpf_ktime_get_ns()). Event::datetime() converts to wall-clock time using boot time from /proc/stat.
Applications that statically link SSL (Claude/Bun uses BoringSSL, NVM Node.js uses OpenSSL) require --binary-path because there's no system libssl.so to hook. When --binary-path is specified:
- sslsniff tries symbol lookup first, then falls back to BoringSSL byte-pattern detection for stripped binaries
- The
--commfilter is NOT passed to sslsniff (only to the process runner) — becausebpf_get_current_comm()returns the thread name, not the process name. Claude's SSL traffic runs on an "HTTP Client" thread, so-c claudewould filter out all SSL traffic.
This logic is in build_trace_agent() in collector/src/cmd_trace.rs.
- Implement
Analyzertrait incollector/src/framework/analyzers/ - Core method:
async fn analyze(&self, events: EventStream) -> EventStream - Export in
analyzers/mod.rs - Attach via
.add_analyzer(Box::new(MyAnalyzer::new()))on any runner
- Implement
Runnertrait incollector/src/framework/runners/ - Use
BinaryExecutorfor running external binaries and parsing JSON output - Use fluent builder pattern for configuration
- Export in
runners/mod.rs
- Create
name.bpf.c(kernel) andname.c(userspace) inbpf/ - Add to
APPSvariable inbpf/Makefile - Use CO-RE pattern with architecture-specific
vmlinux.hfromvmlinux/ - Output JSON to stdout; debug info to stderr
top— Primary live view. Run assudo ./agentsight top; it loads eBPF probes and also reads agent-native sessions when present.record— Optimized recording. Usesudo ./agentsight record -- <command>to launch and trace a command, orsudo ./agentsight record -c <comm>/-p <pid>to attach. It enables SSL, process, stdio when applicable, system monitoring, materialized view sinks, and the web UI by default.stat— Query the latest saved session, or runsudo ./agentsight stat -- <command>and print counters when the command exits.report [summary|token|audit|prompts|export|list]— Query saved local SQLite sessions; these usually do not need sudo.reportwith no subcommand defaults tosummary.debug trace— Most flexible live capture. Toggle--ssl,--process,--stdio,--system, and--serverindependently. Supports--ssl-filter,--http-filter,--binary-path, and--otel.debug ssl/debug process/debug stdio/debug system— Raw component-level debug entrypoints. Usesudobecause they load eBPF probes or inspect privileged process state.
In build_trace_agent(), when SSL is enabled and --binary-path is absent, the binary is auto-discovered from --comm: resolve_binary_path(comm) resolves the binary, and it is adopted only if binary_embeds_ssl() returns true (the binary contains the SSL_write symbol-name string). This fixes record -c node (Node statically links OpenSSL — no system libssl.so to hook) while leaving dynamically-linked runtimes like Python on sslsniff's system-libssl + comm-filter path. record -- <command> resolves the launched command directly because it targets one known process tree.
--binary-path docker://<name|id> (or docker:<name|id>) targets an agent
running in a Docker container. resolve_container_binary_path() in
collector/src/main.rs runs docker inspect --format '{{.State.Pid}}' to get
the container's init PID, then find_ssl_pid_in_tree() walks the descendant
process tree (via /proc/<pid>/task/<pid>/children) and returns the first
process whose /proc/<pid>/exe embeds SSL. This is needed because the init PID
is often a wrapper like tini (OpenClaw runs tini -s -- node openclaw.mjs gateway) that contains no SSL code. The scheme is handled by the trace builder
used by record/debug trace and by raw debug ssl. See
docs/experiment/openclaw.md. parse_container_ref() has unit tests.
- No SSL capture from Claude/Bun: Must use
--binary-pathpointing to the actual binary, or usesudo ./agentsight record -- claudeso AgentSight resolves the launched command. BoringSSL is statically linked and stripped. - No SSL capture from Node.js / Gemini CLI: All Node.js statically links OpenSSL.
record -c nodenow auto-discovers the Node binary;sudo ./agentsight record -- geminialso works. An HTTP/HTTPS proxy does not affect capture (TLS still happens in-process atSSL_*). --commfilter drops all SSL events: SSL runs on "HTTP Client" thread, not the process name thread. Fixed:--commis auto-skipped for sslsniff when--binary-pathis set.- eBPF permission errors: Requires
sudoorCAP_BPF+CAP_SYS_ADMIN. - Port 7395 conflict: Default web server port. Change with
--server-port.