Skip to content
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 into
feature/phase2-upgradesfrom
feat/kora-KR-REASONING-PANEL-EMAIL-XREF
May 23, 2026
Merged

feat(kora): KR-REASONING-PANEL-EMAIL-XREF — extend xref to email outbound#148
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-REASONING-PANEL-EMAIL-XREF

Conversation

@rafe-walker

Copy link
Copy Markdown
Owner

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.jsonl for 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:

Gate Confirmed Source
PurelymailClient.send_email() accepts 6 reasoning meta kwargs purelymail_client.py:257-262
_append_outbound_log() writes them opt-in (mirroring slack_dm #131) purelymail_client.py:611-622
caller_session_id literal format matches engine derivation Handler's _email_caller_session_id at email_inbound_handler.py:271-277 returns f"email:{message_id or 'unknown'}"; matches _derive_caller_session_id at anthropic_engine.py:869-871

What'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 inbound message_id for email:{X} shapes; rejects the email:unknown fallback (no correlation target) and non-email shapes.
  • _find_xref_for_email_group()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's in_reply_to == inbound message_id) — covers writers that drop the primary key (operator scripts / MCP send tool)
    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 (more common in practice); falls through to email when slack parse OR match fails. The 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 decision: email body NEVER in email outbound JSONL (privacy + size). Even on successful xref, response_text_truncated_200 stays null for email-sourced rows. Same shape as slack_dm panel's text carve-out + #143's message_id carve-out. 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.

Test plan

Refs

  • rafe-walker/kora-docs17_cc_bucket_prompts/KR-REASONING-PANEL-EMAIL-XREF_email_path.md
  • PR #146 — KR-EMAIL-OUTBOUND-REASONING-META (unblocked this; closed the STOP-ASK gap)
  • 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 for opt-in reasoning meta)
  • PR #124 — design decision on body absence in email JSONL

🤖 Generated with Claude Code

…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>
@rafe-walker rafe-walker merged commit 98751a4 into feature/phase2-upgrades May 23, 2026
@rafe-walker rafe-walker deleted the feat/kora-KR-REASONING-PANEL-EMAIL-XREF branch May 23, 2026 21:02
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant