This repository was archived by the owner on May 26, 2026. It is now read-only.
feat(kora): KR-REASONING-PANEL-EMAIL-XREF — extend xref to email outbound#148
Merged
rafe-walker merged 1 commit intoMay 23, 2026
Conversation
…ound Cited follow-on from PR #143 (KR-REASONING-PANEL-MODEL-XREF), unblocked by CC#1's PR #146 (KR-EMAIL-OUTBOUND-REASONING-META). K-DG verified BEFORE drafting (re-dispatch from PM after the prior STOP-ASK call): Gate 1 — PurelymailClient.send_email() accepts the 6 reasoning meta kwargs (model_used / input_tokens / output_tokens / reasoning_duration_ms / reasoning_error / caller_session_id) + caller_actor_kind. Confirmed at lines 257-262. ✓ Gate 2 — _append_outbound_log() writes them when set, opt-in shape mirroring slack_dm's PR #131 pattern. Confirmed at lines 611-622. ✓ Gate 3 — caller_session_id literal format matches engine's _derive_caller_session_id for source=email. Engine derives ``f"email:{message_id}"`` (anthropic_engine.py:869-871); the handler at email_inbound_handler.py:271-277 defines ``_email_caller_session_id()`` returning the same shape, and threads it through to ``client.send_email(caller_session_id=...)`` at line 667. ✓ Helper extension (kora_cli/audit/reasoning_xref.py): * _email_outbound_log_path() — re-resolves on every call so monkeypatch in tests works (per #137 fixture lesson). * _load_email_outbound_entries() — reads the email outbound JSONL; tolerates missing file / malformed lines (same discipline as _load_outbound_entries for slack_dm). * _parse_email_session_id(session_id) → message_id or None. Rejects "email:unknown" fallback shape (no correlation target) + non-email-shaped ids. * _find_xref_for_email_group(message_id, ...) — three-tier cascade per spec §3: 1. PRIMARY: caller_session_id literal equality (audit's "email:{msg_id}" == outbound's caller_session_id) 2. SECONDARY: in_reply_to chain (outbound.in_reply_to == inbound message_id) 3. LAST RESORT: closest sent_at within ±60s window (defensive against future writer bugs that drop both correlation keys) * load_reasoning_calls_with_xref() — slack-first precedence per spec. Tries slack_dm xref first (slack_dm is more common in practice); falls through to email when slack parse OR match fails. Cascade is correct regardless of source mix because audit caller_session_id is source-specific (slack_dm and email shapes are mutually-exclusive parses). response_text carve-out for email-sourced rows: * Per CC#1's PR #124 design: body NEVER in email outbound JSONL (privacy + size). Even on successful xref, the response_text_truncated_200 field stays null for email-sourced rows. * Same shape as the slack_dm panel's text carve-out + #143's message_id carve-out. Documented inline in the projection branch so the next reader knows it's intentional, not a drop. * The xref_source local variable tracks which JSONL the match came from; conditional null-set on email guarantees the carve-out can't accidentally regress to populating from a future field rename. Tests (tests/kora_cli/audit/test_reasoning_xref_email.py — 20): * _parse_email_session_id: happy path, "email:unknown" fallback, "email:" empty, non-email shapes * PRIMARY caller_session_id match populates fields * response_text STAYS NULL per #124 design even on success * PRIMARY beats SECONDARY when both present in different outbound entries * SECONDARY in_reply_to fallback works without caller_session_id (operator-script / MCP-send paths) * LAST RESORT timestamp window when both keys absent * Outside ±60s window degrades gracefully * Cost-rung derivation parametrized across opus/sonnet/haiku/ unmapped on the email path * cost_ladder_halted xref supersedes audit status * Slack-first precedence: slack_dm audit prefers slack outbound even when email outbound exists in the same window * Empty email outbound log → email-source rows degrade * Malformed email outbound line tolerated * SECURITY walk-payload (Anthropic / HMAC) on email-enriched output * Endpoint integration end-to-end * Different in_reply_to doesn't cross-correlate Existing tests (slack_dm-only paths from #141/#143) — 42/42 still pass. Slack-first precedence preserved exactly. Full admin-panel + audit regression: 400/400 across 29 suites. Refs: * rafe-walker/kora-docs 17_cc_bucket_prompts/KR-REASONING-PANEL-EMAIL-XREF_email_path.md * PR #146 — KR-EMAIL-OUTBOUND-REASONING-META (unblocked this) * PR #143 — KR-REASONING-PANEL-MODEL-XREF (slack_dm xref helper) * PR #141 — KR-AUDIT-PANEL-ENDPOINTS (reasoning panel audit) * PR #131 — KR-FEAT-AI-RESPONSE-LOOP ST2 (slack_dm precedent) * PR #124 — design decision on body absence in email JSONL Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Cited follow-on from PR #143 (KR-REASONING-PANEL-MODEL-XREF), unblocked by CC#1's PR #146 (KR-EMAIL-OUTBOUND-REASONING-META). Extends the reasoning panel's xref helper to also check
email_outbound_log.jsonlfor AUTO_REPLY reasoning calls. Backend-only; TS shape unchanged.K-DG verified before drafting (re-dispatch after STOP-ASK)
All three gates per the re-dispatch order:
PurelymailClient.send_email()accepts 6 reasoning meta kwargspurelymail_client.py:257-262_append_outbound_log()writes them opt-in (mirroring slack_dm #131)purelymail_client.py:611-622caller_session_idliteral format matches engine derivation_email_caller_session_idatemail_inbound_handler.py:271-277returnsf"email:{message_id or 'unknown'}"; matches_derive_caller_session_idatanthropic_engine.py:869-871What's in here
_email_outbound_log_path()— re-resolves per-call so monkeypatch in tests works (per feat(kora): KR-SLACK-DM-PANEL-FLIP — endpoint reads from JSONL #137 fixture lesson)._load_email_outbound_entries()— reads the email outbound JSONL; tolerates missing file / malformed lines._parse_email_session_id(session_id)— returns inboundmessage_idforemail:{X}shapes; rejects theemail:unknownfallback (no correlation target) and non-email shapes._find_xref_for_email_group()— three-tier cascade per spec §3:caller_session_idliteral equality (audit'semail:{msg_id}== outbound'scaller_session_id)in_reply_tochain (outbound'sin_reply_to == inbound message_id) — covers writers that drop the primary key (operator scripts / MCP send tool)sent_atwithin ±60s window — defensive against future writer bugs that drop both correlation keysload_reasoning_calls_with_xref()— slack-first precedence per spec. Tries slack_dm xref first (more common in practice); falls through to email when slack parse OR match fails. The cascade is correct regardless of source mix because auditcaller_session_idis source-specific (slack_dm and email shapes are mutually-exclusive parses).response_text carve-out for email-sourced rows
Per CC#1's PR #124 design decision: email body NEVER in email outbound JSONL (privacy + size). Even on successful xref,
response_text_truncated_200stays null for email-sourced rows. Same shape as slack_dm panel'stextcarve-out + #143'smessage_idcarve-out. Thexref_sourcelocal variable tracks which JSONL the match came from; conditional null-set on email guarantees the carve-out can't accidentally regress to populating from a future field rename.Test plan
tests/kora_cli/audit/test_reasoning_xref_email.py— 20 tests:_parse_email_session_id(happy + unknown-fallback + non-email shapes), PRIMARY caller_session_id match, response_text null per feat(kora): KR-FEAT-EMAIL ST1 — Purelymail outbound via SMTP #124 design, PRIMARY > SECONDARY precedence, SECONDARY in_reply_to fallback, LAST RESORT timestamp window, outside-±60s degrades, cost-rung derivation parametrized across opus/sonnet/haiku/unmapped on email path,cost_ladder_haltedxref supersedes status, slack-first cross-source precedence, empty email outbound log degrades, malformed line tolerated, walk-payload SECURITY, endpoint integration end-to-end, differentin_reply_todoesn't cross-correlate.Refs
rafe-walker/kora-docs→17_cc_bucket_prompts/KR-REASONING-PANEL-EMAIL-XREF_email_path.md🤖 Generated with Claude Code