Make opt-in body capture log complete request and response I/O #77

Closed
opened 2026-08-04 20:08:16 +00:00 by coilyco-ops · 5 comments
Owner

Decision

Agent Proxy model body capture remains opt-in and defaults off. When PROXY_TRACE_BODIES is enabled, Agent Proxy logs every field in both the complete normalized request body and the complete normalized response body for every routed model call.

There is no selected-field or request-only capture mode after opt-in. Transport credentials and hop-by-hop headers are not model I/O and remain excluded.

Agent Proxy owns this capture. Callers and wrappers such as Sirens Echo retain correlation and operational metadata but do not duplicate model bodies.

Sink boundary

With capture disabled, stdout, OTLP spans, and SigNoz remain metadata-only. With capture enabled, Agent Proxy structured logs and trace attributes carry the complete request and response bodies. Any receiving OTLP or SigNoz sink must therefore be operated as restricted model-content storage.

SigNoz viewing schema

Each captured boundary call emits exactly two structured Agent Proxy log events:

  • model.request.captured with the complete normalized JSON object under request.body
  • model.response.captured with the complete normalized JSON object under response.body

Both events carry agentproxy.capture.schema_version=1, agentproxy.capture.status, agentproxy.request_id, trace_id, and the Agent Proxy request-span span_id. The trace_id + span_id pair joins the events and separates multiple model calls within one Sirens Echo turn.

The request span also carries canonical JSON strings under agentproxy.request.body and agentproxy.response.body. agentproxy.capture.status is complete for a full body. A failed, cancelled, or interrupted response uses agentproxy.capture.status=incomplete, includes every field available at the boundary, and records a closed-set agentproxy.capture.reason.

SigNoz Logs is the primary content viewer. From the Agent Proxy request.chat or request.completions span, the operator uses trace-to-logs, restricts results to the same trace_id, span_id, and service.name=agent-proxy, opens the paired capture events, and expands request.body or response.body. The span attribute panel exposes the same canonical content for direct inspection.

Current gap

  • PROXY_TRACE_BODIES defaults off, as intended.
  • The enabled path captures selected request fields in operational trace and log attributes.
  • It does not capture response bodies.
  • It does not emit the stable paired viewing schema above.
  • It therefore violates the all-fields, both-directions contract when enabled.

Required implementation

  • Preserve the opt-in default-off switch.
  • When enabled, serialize every field in the normalized client request accepted by Agent Proxy.
  • When enabled, serialize every field in the normalized response returned by Agent Proxy, including content, reasoning content, tool calls, usage, finish state, and errors when present.
  • Emit model.request.captured and model.response.captured once per boundary call with the stable fields defined above.
  • Put the canonical JSON strings on the Agent Proxy request span under agentproxy.request.body and agentproxy.response.body.
  • Cover non-streaming chat, reconstructed streaming chat, text completions, and MCP prompt calls.
  • Keep credentials and hop-by-hop transport headers out of capture without applying a model-field allowlist.
  • Never silently degrade enabled capture to selected fields or request-only evidence. Surface a hard capture failure instead.
  • Keep capture ownership in Agent Proxy. Do not add duplicate full-body capture to Sirens Echo or other callers.
  • Add contract and failure-path tests for every supported request surface.

Acceptance

  • With capture disabled, body events and body span attributes are absent.
  • With capture enabled, every normalized request body field is present in request.body and agentproxy.request.body.
  • With capture enabled, every normalized response body field is present in response.body and agentproxy.response.body.
  • The two events share the correct trace_id, request-span span_id, and agentproxy.request_id.
  • Streaming capture reconstructs the complete normalized response returned to the caller.
  • Errors and cancellations record all model I/O available at the boundary plus an explicit incomplete status and closed-set reason.
  • Enabled capture cannot report success after dropping fields or omitting the response.
  • A SigNoz operator can move from the request span to the paired content events through trace-to-logs without a timestamp search.
  • Documentation states the opt-in all-fields, both-directions contract and stable viewing schema.
  • docs/FEATURES.md moves this capability from planned to landed only after code, deployment, and live verification pass.
## Decision Agent Proxy model body capture remains opt-in and defaults off. When `PROXY_TRACE_BODIES` is enabled, Agent Proxy logs every field in both the complete normalized request body and the complete normalized response body for every routed model call. There is no selected-field or request-only capture mode after opt-in. Transport credentials and hop-by-hop headers are not model I/O and remain excluded. Agent Proxy owns this capture. Callers and wrappers such as Sirens Echo retain correlation and operational metadata but do not duplicate model bodies. ## Sink boundary With capture disabled, stdout, OTLP spans, and SigNoz remain metadata-only. With capture enabled, Agent Proxy structured logs and trace attributes carry the complete request and response bodies. Any receiving OTLP or SigNoz sink must therefore be operated as restricted model-content storage. ## SigNoz viewing schema Each captured boundary call emits exactly two structured Agent Proxy log events: * `model.request.captured` with the complete normalized JSON object under `request.body` * `model.response.captured` with the complete normalized JSON object under `response.body` Both events carry `agentproxy.capture.schema_version=1`, `agentproxy.capture.status`, `agentproxy.request_id`, `trace_id`, and the Agent Proxy request-span `span_id`. The `trace_id + span_id` pair joins the events and separates multiple model calls within one Sirens Echo turn. The request span also carries canonical JSON strings under `agentproxy.request.body` and `agentproxy.response.body`. `agentproxy.capture.status` is `complete` for a full body. A failed, cancelled, or interrupted response uses `agentproxy.capture.status=incomplete`, includes every field available at the boundary, and records a closed-set `agentproxy.capture.reason`. SigNoz Logs is the primary content viewer. From the Agent Proxy `request.chat` or `request.completions` span, the operator uses trace-to-logs, restricts results to the same `trace_id`, `span_id`, and `service.name=agent-proxy`, opens the paired capture events, and expands `request.body` or `response.body`. The span attribute panel exposes the same canonical content for direct inspection. ## Current gap * `PROXY_TRACE_BODIES` defaults off, as intended. * The enabled path captures selected request fields in operational trace and log attributes. * It does not capture response bodies. * It does not emit the stable paired viewing schema above. * It therefore violates the all-fields, both-directions contract when enabled. ## Required implementation * Preserve the opt-in default-off switch. * When enabled, serialize every field in the normalized client request accepted by Agent Proxy. * When enabled, serialize every field in the normalized response returned by Agent Proxy, including content, reasoning content, tool calls, usage, finish state, and errors when present. * Emit `model.request.captured` and `model.response.captured` once per boundary call with the stable fields defined above. * Put the canonical JSON strings on the Agent Proxy request span under `agentproxy.request.body` and `agentproxy.response.body`. * Cover non-streaming chat, reconstructed streaming chat, text completions, and MCP prompt calls. * Keep credentials and hop-by-hop transport headers out of capture without applying a model-field allowlist. * Never silently degrade enabled capture to selected fields or request-only evidence. Surface a hard capture failure instead. * Keep capture ownership in Agent Proxy. Do not add duplicate full-body capture to Sirens Echo or other callers. * Add contract and failure-path tests for every supported request surface. ## Acceptance * With capture disabled, body events and body span attributes are absent. * With capture enabled, every normalized request body field is present in `request.body` and `agentproxy.request.body`. * With capture enabled, every normalized response body field is present in `response.body` and `agentproxy.response.body`. * The two events share the correct `trace_id`, request-span `span_id`, and `agentproxy.request_id`. * Streaming capture reconstructs the complete normalized response returned to the caller. * Errors and cancellations record all model I/O available at the boundary plus an explicit incomplete status and closed-set reason. * Enabled capture cannot report success after dropping fields or omitting the response. * A SigNoz operator can move from the request span to the paired content events through trace-to-logs without a timestamp search. * Documentation states the opt-in all-fields, both-directions contract and stable viewing schema. * `docs/FEATURES.md` moves this capability from planned to landed only after code, deployment, and live verification pass.
Author
Owner

The usage contract is now documented on canonical main in commit 6f7a333.

The docs explicitly establish Agent Proxy as the single owner of restricted complete normalized request and response retention, keep stdout/OTLP/SigNoz metadata-only, and require eventual fail-closed durable capture. The implementation and lifecycle controls in this issue remain planned, not claimed as shipped.

The usage contract is now documented on canonical `main` in commit `6f7a333`. The docs explicitly establish Agent Proxy as the single owner of restricted complete normalized request and response retention, keep stdout/OTLP/SigNoz metadata-only, and require eventual fail-closed durable capture. The implementation and lifecycle controls in this issue remain planned, not claimed as shipped.
coilyco-ops changed title from Enforce restricted full-I/O capture as an Agent Proxy usage condition to Make opt-in body capture log complete request and response I/O 2026-08-04 20:22:46 +00:00
Author
Owner

Correction landed on canonical main in commit 0cb5efa.

The contract now says body capture is opt-in and defaults off. When enabled, Agent Proxy must log every field in both the complete normalized request and response bodies. There is no selected-field or request-only mode. The issue title, implementation requirements, and acceptance criteria have been updated to match.

Correction landed on canonical `main` in commit `0cb5efa`. The contract now says body capture is opt-in and defaults off. When enabled, Agent Proxy must log every field in both the complete normalized request and response bodies. There is no selected-field or request-only mode. The issue title, implementation requirements, and acceptance criteria have been updated to match.
Author
Owner

The stable SigNoz content-viewing contract is documented on canonical main in commit 7745866.

It defines the paired model.request.captured and model.response.captured events, request.body and response.body, request-span body attributes, trace_id + span_id pairing, complete versus incomplete capture status, and the trace-to-logs operator flow.

The stable SigNoz content-viewing contract is documented on canonical `main` in commit `7745866`. It defines the paired `model.request.captured` and `model.response.captured` events, `request.body` and `response.body`, request-span body attributes, `trace_id + span_id` pairing, complete versus incomplete capture status, and the trace-to-logs operator flow.
Author
Owner

Engineering implementation landed on canonical main in commit 713d72b.

The repository now provides strict opt-in paired request and response capture for non-streaming chat, reconstructed streaming chat, text completions, and MCP prompt calls. Capture remains default-off. Enabled capture writes canonical complete bodies to the request span and paired structured events, records partial error and cancellation evidence with closed-set reasons, and fails hard on serialization, span, or log-delivery loss.

Repository proof:

  • ward exec test - 231 passed
  • ward exec lint - passed
  • ward exec typecheck - passed
  • ward exec format-check - passed
  • ward exec smoke - passed
  • ward exec boot-probe - passed

Ops closeout remains intentionally open:

  • Promote or deploy commit 713d72b through the established path.
  • Confirm the disabled path emits no body events or body span attributes.
  • With PROXY_TRACE_BODIES enabled and the sink handled as restricted model-content storage, verify exactly one model.request.captured and one model.response.captured event for a routed call.
  • Verify the pair shares agentproxy.request_id, trace_id, and the Agent Proxy request-span span_id, and that trace-to-logs reaches both events without a timestamp search.
  • Verify request.body, response.body, agentproxy.request.body, and agentproxy.response.body contain every expected field.
  • After live proof passes, move the capability from planned to landed in docs/FEATURES.md and close this issue.
Engineering implementation landed on canonical `main` in commit `713d72b`. The repository now provides strict opt-in paired request and response capture for non-streaming chat, reconstructed streaming chat, text completions, and MCP prompt calls. Capture remains default-off. Enabled capture writes canonical complete bodies to the request span and paired structured events, records partial error and cancellation evidence with closed-set reasons, and fails hard on serialization, span, or log-delivery loss. Repository proof: * `ward exec test` - 231 passed * `ward exec lint` - passed * `ward exec typecheck` - passed * `ward exec format-check` - passed * `ward exec smoke` - passed * `ward exec boot-probe` - passed Ops closeout remains intentionally open: * Promote or deploy commit `713d72b` through the established path. * Confirm the disabled path emits no body events or body span attributes. * With `PROXY_TRACE_BODIES` enabled and the sink handled as restricted model-content storage, verify exactly one `model.request.captured` and one `model.response.captured` event for a routed call. * Verify the pair shares `agentproxy.request_id`, `trace_id`, and the Agent Proxy request-span `span_id`, and that trace-to-logs reaches both events without a timestamp search. * Verify `request.body`, `response.body`, `agentproxy.request.body`, and `agentproxy.response.body` contain every expected field. * After live proof passes, move the capability from planned to landed in `docs/FEATURES.md` and close this issue.
Author
Owner

Ops closeout is complete through coilyco-bridge/deploy#322.

  • The restricted ser8 deployment now enables PROXY_TRACE_BODIES while the application and generic chart defaults remain off.
  • A public-safe routed call emitted exactly one complete request capture and one complete response capture.
  • The pair shared agentproxy.request_id, intrinsic trace identity, and the Agent Proxy request-span identity. Trace-to-logs returned both events without a timestamp search.
  • Request and response events retained every expected top-level field. Response evidence included reasoning content, finish state, and usage.
  • The request span carried agentproxy.request.body and agentproxy.response.body with the same expected field sets.
  • Agent Proxy validation passed with 235 tests, lint, type checking, format checking, smoke, and the frozen-runtime boot probe. Documentation closeout commit b5284a4 passed CI run 63.
Ops closeout is complete through coilyco-bridge/deploy#322. * The restricted ser8 deployment now enables PROXY_TRACE_BODIES while the application and generic chart defaults remain off. * A public-safe routed call emitted exactly one complete request capture and one complete response capture. * The pair shared agentproxy.request_id, intrinsic trace identity, and the Agent Proxy request-span identity. Trace-to-logs returned both events without a timestamp search. * Request and response events retained every expected top-level field. Response evidence included reasoning content, finish state, and usage. * The request span carried agentproxy.request.body and agentproxy.response.body with the same expected field sets. * Agent Proxy validation passed with 235 tests, lint, type checking, format checking, smoke, and the frozen-runtime boot probe. Documentation closeout commit b5284a4 passed CI run 63.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/agent-proxy#77
No description provided.