Skip to content

Security & Event Observability Schema

This document defines the field naming conventions and cardinality constraints for Kairo's event-bus → OpenTelemetry integration.

Status: v0.9 updates the schema to cover every KairoEventBus domain, not just security. The original security.* keys predate the bus (see ADR-018) and are preserved verbatim inside the envelope body for backwards compatibility with existing LoggingSecurityEventSink consumers. When the envelope is bridged to OpenTelemetry by KairoEventOTelExporter, a new namespace prefix kairo.<domain>.* is applied on top.

Attribute Namespace

Every log record emitted by KairoEventOTelExporter carries three envelope-level keys:

KeyTypeExample
kairo.event.idString (UUID)9c1b…-…-…
kairo.domainString (enum)security, execution, evolution, team
kairo.event.typeStringGUARDRAIL_DENY, MODEL_TURN, EXPERT_TEAM_ROUND

Additional per-domain attributes are flattened under kairo.<domain>.<key> — the exporter copies each entry of KairoEvent.attributes() into this namespace. Values are converted to strings; structured payloads MUST be flattened by the publisher before entering the bus.

Severity mapping:

  • securityWARN
  • execution / evolution / teamINFO

LogRecord.severityText is set to the domain name; LogRecord.body is set to the event type; LogRecord.observedTimestamp uses KairoEvent.timestamp().

Domain Schemas

kairo.security.*

Used for guardrail lifecycle events. Always-on in the OTel exporter (never sampled out).

FieldTypeDescriptionExample
kairo.security.event.typeSecurityEventType enumClassification of the security decisionGUARDRAIL_DENY
kairo.security.policy.nameString (bounded set)Policy that produced the decisioncontent-filter
kairo.security.decision.actionALLOW / DENY / MODIFY / WARNOutcome of the policyDENY
kairo.security.decision.reasonString (max 256 chars)Human-readable reasonPII detected in output
kairo.security.target.nameString (bounded set)Tool or model being guardedecho, claude-3
kairo.security.target.typeMODEL / TOOL / MCP_TOOLType of targetTOOL
kairo.security.agent.nameString (bounded set)Agent owning the pipelineassistant
kairo.security.guardrail.phaseGuardrailPhase enumPipeline boundary pointPRE_TOOL

kairo.execution.*

Used for execution-log lifecycle (model turns, tool calls, compaction, iterations). Sampled via kairo.observability.event-otel.sampling-ratio.

FieldTypeDescriptionExample
kairo.execution.modelString (bounded set)Model identifierclaude-opus-4-7
kairo.execution.toolString (bounded set)Tool name when applicablebash, read
kairo.execution.phaseEnumLifecycle phaseMODEL_TURN, TOOL_CALL, COMPACT
kairo.execution.iterationIntegerIteration ordinal within a run3
kairo.execution.tokens.inputIntegerInput tokens12540
kairo.execution.tokens.outputIntegerOutput tokens812
kairo.execution.duration_msIntegerElapsed ms for the span1824

kairo.evolution.*

Used for self-evolution skill governance lifecycle (proposal → review → apply).

FieldTypeDescriptionExample
kairo.evolution.skill.idString (bounded set)Skill identifiersummarize-diff
kairo.evolution.phaseEnumGovernance phasePROPOSED, REVIEWED, APPLIED, ROLLED_BACK
kairo.evolution.reviewerString (bounded set)Reviewer principalsre-ops
kairo.evolution.outcomeEnumReview outcomeACCEPTED, REJECTED

kairo.team.*

Used for multi-agent orchestration lifecycle (Expert Team, v0.10+ surfaces populate it).

FieldTypeDescriptionExample
kairo.team.idString (bounded set)Team identifiertriage-team
kairo.team.roundIntegerRound ordinal within a conversation2
kairo.team.roleString (bounded set)Contributing roletriage, analyst, reporter
kairo.team.expert.idString (bounded set)Expert agent idlog-reader
kairo.team.transitionEnumState transitionROUND_START, ROUND_END, HANDOFF, CONSENSUS

Low-Cardinality Constraints

Every domain above keeps attribute values inside a bounded, enumerable set:

  • No request IDs, trace IDs, or UUIDs as attribute values (except kairo.event.id which is intentional and low-volume per-event).
  • No raw input content (prompts, tool arguments, model responses) — these MUST stay in the original bus payload, which is not exported.
  • No unbounded user-supplied strings.

Attributes that would otherwise be high-cardinality MUST be bucketed (e.g. duration → histogram on the OTel SDK side; user id → role).

Attribute Redaction

kairo.observability.event-otel.redact-attribute-patterns is a list of regexes matched against the flat key (after namespacing). When a pattern matches, the value is replaced by the literal string <redacted> before being attached to the log record. Typical starter configuration:

yaml
kairo:
  observability:
    event-otel:
      redact-attribute-patterns:
        - ".*password.*"
        - ".*token.*"
        - ".*secret.*"

Redaction happens in the exporter, not the publisher — the bus still sees raw attribute values, but external observability backends never do.

OTel Integration (v0.9)

The kairo-spring-boot-starter-observability module wires KairoEventOTelExporter when:

  1. kairo.observability.event-otel.enabled=true (default false, matching every other v0.9 starter).
  2. A KairoEventBus bean is present (always true when kairo-spring-boot-starter-core is on the classpath).
  3. A LoggerProvider bean is present (applications bring their own OTel SDK wiring — either via opentelemetry-spring-boot-starter or manual configuration).

Default include-domains is [security]. Execution/team/evolution domains require explicit opt-in:

yaml
kairo:
  observability:
    event-otel:
      enabled: true
      include-domains: [security, execution, evolution]
      sampling-ratio: 0.2     # sampled for non-security; security is always-on

The previous LoggingSecurityEventSink still works in parallel — it writes structured SLF4J entries with the legacy security.* keys (no kairo. prefix), so existing dashboards keep functioning while teams migrate to the OTel path. See ADR-018 for the domain/envelope contract and ADR-022 for the exporter's behavioural guarantees.