Skip to content

feat(providers): wire IOJournalRecorder into AnthropicAdapter (M3 / slice 3 of #517)#535

Merged
Q00 merged 1 commit into
Q00:mainfrom
shaun0927:feat/517-3-anthropic-io-recorder
May 2, 2026
Merged

feat(providers): wire IOJournalRecorder into AnthropicAdapter (M3 / slice 3 of #517)#535
Q00 merged 1 commit into
Q00:mainfrom
shaun0927:feat/517-3-anthropic-io-recorder

Conversation

@shaun0927
Copy link
Copy Markdown
Collaborator

Summary

Third slice of #517: wires the IOJournalRecorder (#534) into AnthropicAdapter. The constructor now accepts an optional io_recorder keyword; when provided, every client.messages.create call is wrapped in the recorder so paired llm.call.requested / llm.call.returned events land on the EventStore.

The change is purely additive: legacy constructor calls without io_recorder= continue to work byte-for-byte (default None skips emission entirely).

Stack notice. Depends on #534 (recorder helper) → #532 (foundation).

Changes

  • src/ouroboros/providers/anthropic_adapter.py
    • Constructor adds io_recorder: IOJournalRecorder | None = None (last keyword, default None).
    • complete() wraps client.messages.create in recorder.record_llm_call(...) only when a recorder is configured. The legacy code path is preserved verbatim for the no-recorder case.
    • Two module-level helpers (_serialise_prompt_for_hash, _record_completion) keep the recorder wiring tidy.
  • tests/unit/providers/test_anthropic_adapter_io_recorder.py — 8 cases covering both branches plus the exception path.

Verification

Check Result
uv run ruff check ... clean
uv run ruff format ... 1 file reformatted, no logic change
uv run pytest tests/unit/providers/test_anthropic_adapter_io_recorder.py 8 passed

Pre-merge checklist

  • Constructor accepts optional io_recorder kwarg; legacy callers unchanged
  • complete() emits paired requested/returned events with shared call_id when recorder present
  • No-recorder path emits nothing (test pins this)
  • Exception path emits returned with is_error=True + error_kind
  • Adapter still returns Result.err via the existing _handle_error on exception
  • No change to _parse_response or response shape

Post-merge checklist

  • Slices 4–6: same migration pattern for LiteLLM, Claude Code, Codex CLI, Gemini CLI, OpenCode adapters
  • Slice 7: MCP tool dispatch path uses record_tool_call
  • Final slice: M3 acceptance scenario (closes M3 I/O Journal — tool.call.* and llm.call.* event categories #517)
  • Composition root passes a per-execution IOJournalRecorder into adapters where wanted

Rollback

Constructor kwarg is optional with a None default. Rollback removes the kwarg + helpers; no caller depends on the new behaviour yet.

Stack: depends on #534#532.

Copy link
Copy Markdown
Contributor

@ouroboros-agent ouroboros-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — ouroboros-agent[bot]

Verdict: REQUEST_CHANGES

Reviewing commit 31047e8 for PR #535

Review record: 984da14d-fc59-4d25-95fb-176c059cc527

Blocking Findings

| # | File:Line | Severity | Finding |
|### Recovery Notes
First recoverable review artifact generated from codex analysis log.

---|-----------|----------|---------|
| 1 | src/ouroboros/providers/anthropic_adapter.py:216 | BLOCKING | The Anthropic journal emission does not record top_p or stop_sequences, even though both are sent to client.messages.create() and can materially change model behavior. As written, two different outbound requests can produce indistinguishable llm.call.requested events because those fields are neither persisted nor included in prompt_hash, which breaks the PR’s stated “reconstructable from the journal alone” contract. |

Follow-up Findings

  • src/ouroboros/providers/anthropic_adapter.py:44 [warning] _serialise_prompt_for_hash() hashes system_parts as a list, but the actual Anthropic request normalizes them into a single string via \"\\n\\n\".join(system_parts). That means identical wire requests can hash differently depending only on how callers split system text across MessageRole.SYSTEM entries, so prompt_hash is not stable for the real outbound payload.

Non-blocking Suggestions

None.

Design Notes

The recorder/event-factory split is clean and the adapter integration stays additive, but the current Anthropic wiring does not yet preserve enough of the real request shape to make the journal a reliable replay/debug artifact.


Reviewed by ouroboros-agent[bot] via Codex deep analysis

Copy link
Copy Markdown
Contributor

@ouroboros-agent ouroboros-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — ouroboros-agent[bot]

Verdict: REQUEST_CHANGES

Reviewing commit c404890 for PR #535

Review record: bb3d1900-ea52-4a30-b8fa-a92a4e85847a

Blocking Findings

| # | File:Line | Severity | Finding |
|### Recovery Notes
First recoverable review artifact generated from codex analysis log.

---|-----------|----------|---------|
| 1 | src/ouroboros/events/io.py:282,390,452 | BLOCKING | The event factories reshape previews even when callers have already applied recorder-specific privacy/cap settings. IOJournalRecorder precomputes previews with its explicit privacy, preview_cap, and preview_hard_cap overrides before calling these factories (src/ouroboros/events/io_recorder.py:196-200, 233-237, 285-289, 320-324), but the factories call shape_preview() again with default settings. That means a valid override like privacy=PrivacyMode.ON with OUROBOROS_IO_JOURNAL_PREVIEWS=off still loses the preview, and any preview_cap larger than 256 is silently truncated back to 256 on persistence. This breaks the recorder API contract and makes the emitted journal data differ from the caller’s requested policy. |

Non-blocking Suggestions

None.

Design Notes

The split between pure event factories and an async recorder is a good direction, and the Anthropic integration keeps the old call path intact when no recorder is configured. The main design issue is that preview policy is enforced in two layers, so the factory/recorder boundary is currently not a single source of truth.


Reviewed by ouroboros-agent[bot] via Codex deep analysis

@shaun0927
Copy link
Copy Markdown
Collaborator Author

Autopilot follow-up: pushed review-addressing commits and confirmed the GitHub check suite is green on the latest head. Current merge state is clean; remaining CHANGES_REQUESTED status appears to be awaiting fresh bot/human re-review rather than failing CI.

Copy link
Copy Markdown
Contributor

@ouroboros-agent ouroboros-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — ouroboros-agent[bot]

Verdict: APPROVE

Reviewing commit 6aa0380 for PR #535

Review record: 4215e59d-c018-49f9-98a9-3bd5321f2c5f

Blocking Findings

No in-scope blocking findings remained after policy filtering.

Follow-up Findings

  • src/ouroboros/providers/anthropic_adapter.py:30 [warning] _serialise_prompt_for_hash() claims to hash the request, but the payload it builds excludes core request parameters like model, max_tokens, and temperature while the caller only passes top_p and stop_sequences at src/ouroboros/providers/anthropic_adapter.py:244. That means two materially different Anthropic calls can persist the same prompt_hash, which makes the journal misleading for replay/debugging and breaks the “same request => same hash” invariant this PR is introducing.

Non-blocking Suggestions

None.

Design Notes

The recorder/factory split is clean and the best-effort append behavior is sensible. The main weakness is that the adapter-specific prompt serialization does not yet preserve full request identity, which undercuts the value of the new journal fields.

Policy Notes

  • No in-scope blocking findings remained after policy filtering; downgraded verdict accordingly.

Recovery Notes

First recoverable review artifact generated from codex analysis log.


Reviewed by ouroboros-agent[bot] via Codex deep analysis

Copy link
Copy Markdown
Owner

@Q00 Q00 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the AnthropicAdapter IOJournalRecorder wiring. The optional recorder path preserves existing behavior and records both success and provider error paths cleanly.

@Q00 Q00 force-pushed the feat/517-3-anthropic-io-recorder branch from 52b61cd to 44a017e Compare May 2, 2026 19:56
@Q00 Q00 merged commit b9fdc73 into Q00:main May 2, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

M3 I/O Journal — tool.call.* and llm.call.* event categories

2 participants