Integrations¶
ShadowClaw exports over OpenTelemetry and touches nothing inside the products it reports into.
Choosing a path¶
| You want | Use | Needs |
|---|---|---|
| Findings in Grafana, minimum moving parts | Loki direct | A Loki 3.x binary. No Docker, no collector, no root. |
| Findings beside DefenseClaw's telemetry | DefenseClaw stack | Docker, because that stack is a Compose project. |
| Routing, batching, fan-out to several backends | A Collector | scripts/install-otelcol.sh — a 329 MB contrib build. |
| Fleet alerting and long retention | Splunk HEC | A Collector in front of it. |
| Nothing external at all | The local ledger | Nothing. It is always written first. |
Only one collector can own the port
DefenseClaw's bundled collector and ShadowClaw's both bind 127.0.0.1:4317 and 127.0.0.1:4318. Do not run both.
What every integration has in common¶
Identity is preserved on the wire. Every export carries service.name=shadowclaw and service.namespace=shadowclaw, and every log event uses a shadowclaw.* event name.
That is deliberate rather than incidental. ShadowClaw could emit under DefenseClaw's service identity and metric names, which would make its findings appear inside DefenseClaw's native AI Discovery panels — but its detections would then be indistinguishable from DefenseClaw's own, and neither tool's telemetry would be reliable as evidence. In a detector whose attribution is tamper-evident by design, that trade is not worth making.
The two data sets are kept separate on the wire and correlated only at the presentation layer.
Nothing in another product's installation is modified. Collector ports are used as designed. Grafana dashboards are installed through Grafana's HTTP API rather than a provisioning directory. DefenseClaw destinations are added and removed through DefenseClaw's own CLI.
The ledger is written first. Every integration below is downstream of a durable local record, so an outage in any of them costs you nothing.
Secrets come from the environment. The Splunk HEC token is read from SPLUNK_HEC_TOKEN, never from a committed file.
The five log event shapes¶
Every integration reads some subset of these. They are not interchangeable, and a query that does not filter on event_name will silently mix them.
| Event | One record per | Notes |
|---|---|---|
shadowclaw.finding.recorded |
Scored finding | Carries a single headline provider. |
shadowclaw.provider.reached |
Distinct provider reached | Exists because a finding names one provider and a host reaches several. |
shadowclaw.agent.activity |
Newly observed tactic | The incident timeline. Empty without --esf under root. |
shadowclaw.ktp.risk_factors |
Poll | Emitted on quiet polls too. |
shadowclaw.ktp.envelope |
Attributed agent action | Supervision floor per action. |
All five carry a flat JSON body — no nested objects, no arrays — because Grafana parses the line with | json, which reaches neither. Multi-valued fields like signals and degraded_inputs are comma-joined strings that LogQL matches with a regex.
Full field lists in Event schemas.
Two rules that will bite you otherwise¶
Do not group findings by provider¶
A finding carries one headline provider, chosen by ranking its endpoints. One agent process talking to two providers produces one finding, so grouping by (provider) over shadowclaw.finding.recorded counts only the winner — and because the choice is a sort rather than a race, the loser is invisible permanently.
Observed on a live agent swarm: a single Python process reaching Anthropic and Fireworks headlined Anthropic 584 times out of 584 and Fireworks never, purely because api.anthropic.com sorts before api.fireworks.ai.
The failure mode is the dangerous kind — not an error, not an empty panel, just a smaller number that looks plausible. Read shadowclaw.provider.reached for provider questions.
Never panel a KTP Risk Factor without its coverage¶
Each factor is a stress term in [0,1] where 1 is maximum stress, and anything the sensor could not observe reports 1.0, never 0. On an unprivileged sensor adversarial_pressure never drops below 0.714.
# Wrong. Fires permanently on any unprivileged host.
ktp_risk_factor_adversarial_pressure > 0.7
# Right. Fires on a hostile environment, not on a blind sensor.
ktp_risk_factor_adversarial_pressure{ktp_degraded="false"} > 0.7
# Worth its own alert, at far lower urgency: the sensor went partly blind.
ktp_risk_factor_adversarial_pressure{ktp_degraded="true"}
See Risk Factors.