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