Story-1442: Atlas Logfire content gate

This Story makes Atlas's GenAI observability safe by default: model telemetry can still reach Logfire, but prompt, response, tool, evidence, and legal content stay out of ordinary span attributes and out of the general X-Ray/CloudWatch lane.

Mental model

Metadata is the road; content is sealed cargo

Metadata such as service.name, agent.key, gen_ai.agent.name, run_id, form_id, model id, token counts, and cost can travel on normal telemetry. The actual matter text is different: it is privileged cargo and must not be attached to general spans.

real matter run
  -> event_mode="attributes"
  -> include_content=false
  -> no prompt/response/tool/evidence content on spans

synthetic staging opt-in
  -> PRAMAAN_LOGFIRE_CONTENT_CAPTURE=synthetic
  -> LOGFIRE_ENVIRONMENT=staging or synthetic
  -> event_mode="logs"
  -> content leaves only as capped Logfire log events
Implementation

One gate in observability.py

agents/atlas/observability.py now builds a small GenAIInstrumentationPolicy before calling Agent.instrument_all(...). The default policy forces OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=false, keeps event_mode="attributes", and passes include_content=false.

The only content-on path requires the explicit synthetic opt-in and a non-production environment. That path uses a dedicated Logfire logger provider aimed at /v1/logs, with event_mode="logs" and include_binary_content=false.

Guardrails

What stays unchanged

Pydantic AI still owns T1 model-call instrumentation. Atlas still enriches library-owned spans with safe T2 dimensions. No new manual span name was added, no raw tracer call was introduced, and firm_id remains a span attribute only, never baggage.

Synthetic content log events are capped at 800 characters before OTLP export. The cap sits in the Logfire-only log processor, not in the general telemetry path.

Verification

Tests that pin the gate

test_atlas_observability.py proves default content capture is off, synthetic staging capture uses log events, production rejects the synthetic opt-in, and long content is capped before export.