Skip to content

OTLP export

shadowclaw/otlp.py speaks OTLP/HTTP protobuf-JSON directly over urllib. There is no OpenTelemetry SDK in the dependency graph, because there is no dependency graph — see Architecture.

Endpoints

Setting Default
otlp_endpoint http://127.0.0.1:4318

The exporter appends the standard paths:

Signal Path Enabled by
Logs /v1/logs emit_otlp_logs
Metrics /v1/metrics emit_otlp_metrics
# A collector
python3 -m shadowclaw --otlp-endpoint http://127.0.0.1:4318

# Loki 3.x direct — logs only
python3 -m shadowclaw --otlp-endpoint http://127.0.0.1:3100/otlp --no-otlp-metrics

# Nothing at all
python3 -m shadowclaw --no-otlp

Transport rules

Plaintext is refused except to loopback. The exporter will not send http:// to anything but a loopback address. This telemetry names the AI endpoints your staff reach and must not cross a routable interface unencrypted.

A collector that is down is tolerated. A receiver restarting mid-shift must not cost findings. The consequence is that a sensor aimed at a dead port exports nothing, indefinitely, in silence — which is exactly why there is a startup check for it.

The ledger is written first. Export is always downstream of a durable local record, so none of the above can cost you evidence.

Resource attributes

Every log record and every metric carries the same resource:

Attribute Value
service.name shadowclaw
service.namespace shadowclaw
shadowclaw.product Product name.
shadowclaw.author Mike Storm
shadowclaw.author.title, shadowclaw.author.credential Title and credential.
shadowclaw.copyright Copyright notice.
shadowclaw.attribution.intact Official-build provenance indicator; false if the identity digest no longer matches.
host.name Host identity.
os.type Derived, not hardcoded.

service.namespace is set explicitly so DefenseClaw's collector — which inserts service.namespace=defenseclaw on any resource arriving without one — cannot relabel these detections as another product's. See DefenseClaw.

os.type was once the literal string "darwin" in otlp.py, which made every record from a non-macOS host a false statement about itself. It now comes from the platform layer.

Official-build provenance on resource attributes rather than record attributes is deliberate: it survives being forwarded, so the author reaches Splunk intact. shadowclaw.attribution.intact is not a license-compliance signal. See Authorship.

The five log event shapes

Event One record per
shadowclaw.finding.recorded Scored finding.
shadowclaw.provider.reached Distinct provider reached.
shadowclaw.agent.activity Newly observed tactic.
shadowclaw.ktp.risk_factors Poll.
shadowclaw.ktp.envelope Attributed agent action.

All five carry a flat body — no nested objects, no arrays — because consumers parse the line with | json, which reaches neither. Multi-valued fields are comma-joined strings that LogQL and SPL match with a regex.

Full field lists in Event schemas.

Metrics

Gauges and monotonic counters, emitted every poll. Notable ones:

Metric Kind Why it matters
shadowclaw.sensor.up gauge Liveness.
shadowclaw.findings.active gauge Current finding count.
shadowclaw.risk.score gauge Per finding.
shadowclaw.esf.running gauge Emitted even when zero.
shadowclaw.esf.events.received / .dropped counter Queue health.
shadowclaw.agent.tactic.observed counter Host-plane rate.
ktp.risk_factor.* gauge Carries ktp.degraded as an attribute.

Full list in Metrics.

The two shipped Collector pipelines

The Collector is optional — Loki 3.x accepts OTLP directly, and the contrib build is a 329 MB download. Install it when you need routing, batching, fan-out to several backends, or make validate.

./scripts/install-otelcol.sh

The shadow-AI baseline, validated against Collector 0.158.0.

Pipeline Contents
metrics/ai-processes hostmetrics process scraper.
metrics/host Host-wide network and load.
metrics/sensor The sensor's own shadowclaw.* metrics.
logs/findings finding.recorded and provider.reached.

Exporters: debug, rotating local files, optional Splunk HEC, optional upstream OTLP backend.

The host plane. A second complete config rather than edits to the first.

Pipeline Contents
logs/agentic agent.activity, with ATT&CK enrichment.
logs/findings As above.
metrics/sensor As above.
metrics/derived count connector output — fleet tactic and finding rates.

The transform processor derives technique URLs and ATT&CK tactic names.

Why two configs instead of one with a flag

Host-plane detection is opt-in, and turning it on must not be able to regress a working shadow-AI deployment. Same reasoning as the Splunk split.

ATT&CK enrichment lives in the Collector because those mappings are the part most likely to need correcting — and correcting them there does not mean shipping a new sensor to every endpoint.

./bin/otelcol --config config/otel-collector.agentic.yaml
# or
./scripts/run-collector.sh
make validate

Only one collector can own the port

Both configs bind 127.0.0.1:4317 and 127.0.0.1:4318 — the same ports DefenseClaw's bundled collector uses. Do not run both.

Deciding what to send where

Backend Logs Metrics
Loki 3.x direct ✅ /otlp/v1/logs ❌ 404s — use --no-otlp-metrics
A Collector ✅ ✅
DefenseClaw's bundled collector ✅ ✅ leave them on — it has a Prometheus pipeline
Splunk HEC ✅ via Collector ✅ via Collector

Next