Skip to content
This repository was archived by the owner on May 26, 2026. It is now read-only.

feat(kora): KR-FEAT-AI-RESPONSE-LOOP ST2 — handler swap + cred priority flip + anthropic runtime dep (re-opened post #129)#131

Merged
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-FEAT-AI-RESPONSE-LOOP-ST2
May 23, 2026
Merged

feat(kora): KR-FEAT-AI-RESPONSE-LOOP ST2 — handler swap + cred priority flip + anthropic runtime dep (re-opened post #129)#131
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-FEAT-AI-RESPONSE-LOOP-ST2

Conversation

@rafe-walker

Copy link
Copy Markdown
Owner

Re-opened replacement for PR #129 which was closed unmerged due to a PM-tooling race (merge API failure + unconditional branch-delete in same bash block — see feedback_pm_merge_then_delete_race memory).

What changed in the recovery rebase

Original PR #129 context

KR-FEAT-AI-RESPONSE-LOOP ST2 — Phase 2 Feature 5 closes. Joshua DMs Kora → Kora reasons → Kora replies intelligently.

Three rulings landed:

  1. Cascade flipped OAuth-first — CLAUDE_CODE_OAUTH_TOKEN → KORA_ANTHROPIC_API_KEY → fail-CLOSED.
  2. anthropic==0.86.0 moved to core deps.
  3. PM-opens Q1-Q4 from ST1 carried.

Wired: NEW reasoning_engine_listener.py + current_reasoning_engine() accessor + handler swap echo→ReasoningEngine + 5 distinct canned-fallback paths + JSONL extended with model_used/tokens/duration/error + cost-ladder write integration (record_inference). 332/332 tests.

Closes Phase 2 Feature 5.

…ty flip + anthropic runtime dep

Closes Phase 2 Feature 5's biggest user-facing milestone. Joshua
DMs Kora → Kora reasons (real LLM call, system-prompt-driven,
context-aware, cost-ladder-aware) → Kora replies with the
reasoning output. Echo path is gone.

**Ruling 1 — Credential cascade FLIPPED to OAuth-first.**

ST1 shipped `KORA_ANTHROPIC_API_KEY → CLAUDE_CODE_OAUTH_TOKEN`
order. ST2 flips to `CLAUDE_CODE_OAUTH_TOKEN → KORA_ANTHROPIC_API_KEY
→ fail-CLOSED`. Reasoning: Joshua's Max 20x + post-May-15 SDK
billing split route the $200/mo Agent SDK pool via the OAuth path.
OAuth = production; API key = test/dev escape hatch. Engine's
`_auth_mode` test updated to assert OAuth wins when both are set.

**Ruling 2 — `anthropic` to runtime deps.**

Same lesson as KR-SLOWAPI-DEP-FIX + aiosmtplib promotion: the
reasoning_engine listener imports anthropic unconditionally at
boot, so it belongs in the core dependencies block, not under
`[anthropic]` or `[web]` extras. Both prior locations removed;
single declaration in core deps.

**Ruling 3 — §4 PM-opens locked from ST1.**

Q1 system prompt first-draft (composed in ST1); Q2 10-turn context;
Q3 NO retry on 5xx; Q4 PAUSED state gating. All carry through ST2.

**`kora_cli/listeners/reasoning_engine_listener.py`** (~135 lines):

- `ReasoningEngineListener` wrapping the engine in the
  DaemonCoordinator lifecycle. Startup constructs
  `AnthropicReasoningEngine`; **construction failure RE-RAISES**
  so the coordinator aborts boot per the spec's "engine startup
  failure → daemon fails-CLOSED" requirement (a daemon that
  can't reason can't fulfill its primary purpose; better to abort
  loudly than ship a fallback-only daemon).
- Module-level `current_reasoning_engine()` accessor mirroring
  `current_pool()` from KR-MCP-CONSUMPTION ST1 — cross-cutting
  read from `SlackDMHandler` without import coupling.
- `_set_singleton` / `_clear_singleton` private helpers (same
  shape as the MCP pool listener).
- Shutdown closes the engine's HTTP client best-effort; per-listener
  timeout (10s) caps the wait.
- Registered via `register_daemon_listener("reasoning_engine", _factory)`
  at import time; wired into `kora_cli/listeners/__init__.py`.

**`kora_cli/handlers/slack_dm_handler.py`** (+350 lines):

- New optional `reasoning_engine` constructor arg (test injection).
  Production resolves the daemon singleton via
  `current_reasoning_engine()` at reply time.
- Method `_send_echo_reply` (name kept for diff hygiene) body
  swapped: builds `ConversationContext` via
  `load_slack_dm_context()`, constructs `IncomingMessage`, calls
  `engine.respond(...)`, projects `ResponseResult` into reply
  text + JSONL meta.
- **Canned fallback** `"Kora is currently unable to respond;
  operator notified."` sent when:
  - No engine resolvable (test path / partial daemon boot)
  - `ResponseResult.error` set (cost_ladder_halted /
    operational_state_paused / sdk_5xx / ...)
  - Engine itself raises (defensive — recorded as
    `engine_exception:<class>`)
  - Engine returns empty text on success (defensive —
    `empty_response_text`)
- Reply failure does NOT crash the inbound handler — Slack
  always gets 200 OK (preserves the KR-FEAT-SLACK-DM ST2
  belt-and-suspenders guarantee).

5 new optional fields on outbound entries (backwards-compatible —
pre-ST2 entries simply omit them):

- `model_used`: e.g. `"claude-opus-4-7"`
- `input_tokens` / `output_tokens`: from SDK usage
- `reasoning_duration_ms`: engine-side wall-clock
- `reasoning_error`: stable error code (None on success)

Each field is written only when non-None, so canned-fallback
entries that lack reasoning data stay lean.

After a successful reasoning call (NOT canned fallback), the
handler builds `CanonicalUsage(input_tokens, output_tokens)`
and calls `holder.record_inference(canonical_usage, model_name,
provider="anthropic")`. Fail-soft layers:

- No cost holder initialized → silently skip (test paths)
- Zero tokens → skip (no real inference happened)
- `record_inference` itself is fail-soft per its docstring
  (pricing-lookup miss accumulates 0)

K-DG verified literal API: `record_inference(canonical_usage, *,
model_name, provider, base_url)` per
`agent/cost_state_holder.py:272`; `CanonicalUsage` shape per
`agent/usage_pricing.py:30` (input_tokens / output_tokens /
cache_read_tokens / cache_write_tokens / reasoning_tokens /
request_count / raw_usage).

**`test_reasoning_engine_listener.py`** (8 tests):
- current_reasoning_engine() None pre-startup
- Startup with injected engine sets singleton
- Shutdown clears singleton + closes engine
- Shutdown safe when engine.close() raises
- Shutdown safe without prior startup (no-op)
- **Production startup re-raises on misconfig** (fail-CLOSED
  asserted — both credential envs unset → coordinator boot abort)
- Registered in LISTENER_REGISTRY as "reasoning_engine"
- _set_singleton / _clear_singleton helpers tested

**`test_slack_dm_reply.py`** updated/extended (15 ST2 tests):
- ST1 echo-format tests rewritten to assert engine's response
  text (not echo construction)
- `test_oauth_wins_over_api_key_when_both_set` — Ruling 1 verified
- Engine unavailable → canned fallback + reasoning_error="engine_unavailable"
- Engine returns error (cost_ladder_halted / operational_state_paused)
  → canned fallback + recorded error code
- Engine raises exception → canned fallback +
  reasoning_error="engine_exception:<class>"
- Empty engine text on success → canned fallback + "empty_response_text"
- Engine receives correct IncomingMessage metadata (source /
  channel_id / thread_ts / user_id / event_ts)
- **Cost-ladder write called on success** with correct
  CanonicalUsage + model_name + provider="anthropic"
- Cost-ladder write SKIPPED on canned fallback (no real inference)
- Cost-ladder gracefully skips when holder uninitialized

- [x] Base `feature/phase2-upgrades`
- [x] Title `feat(kora): KR-FEAT-AI-RESPONSE-LOOP ST2 — handler swap + cred priority flip + anthropic runtime dep`
- [x] §4 PM-opens carried from ST1 (all defaults locked)
- [x] Credential cascade flipped OAuth-first per Ruling 1
- [x] anthropic moved to runtime deps per Ruling 2
- [x] API key NEVER logged (existing ST1 diverse-failure test
      still holds — credential value never in any error / log path)
- [x] Cost-ladder write integration verified (new test)
- [x] PAUSED state respected (engine refuses; handler sends canned)
- [x] Engine startup failure → daemon fails-CLOSED (re-raise asserted)
- [x] Engine failure paths → canned fallback (5 distinct paths tested)
- [x] K-DG: literal field names pasted from grep:
      `record_inference(canonical_usage, *, model_name, provider, base_url)`,
      `CanonicalUsage` shape, `get_cost_holder()` accessor,
      `_HOLDER` reset helper
- [x] Tests pass locally (**332/332** across full suite)

After ST2 merges, **Phase 2 Feature 5 closes**. Joshua DMs Kora,
Kora reasons (real Anthropic API call against the Max plan pool,
cost-ladder-aware model selection, operational-state-respecting,
10-turn-context-aware), Kora replies intelligently.

PM picks next bucket — candidates:
- **KR-FEAT-AGENTIC-REASONING**: Kora can call kora__* tools inside
  her reasoning (look up ledger, check sea_tickets) for richer
  responses
- **KR-MCP-SEND-TOOLS**: expose kora__send_slack_dm + kora__send_email
  via /mcp for agent-driven sends
- **Substrate-backed conversation memory**: persist context to
  IsoKron substrate, not just JSONL

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rafe-walker rafe-walker merged commit aa00c3a into feature/phase2-upgrades May 23, 2026
@rafe-walker rafe-walker deleted the feat/kora-KR-FEAT-AI-RESPONSE-LOOP-ST2 branch May 23, 2026 00:17
rafe-walker added a commit that referenced this pull request May 23, 2026
…#130)

Agent-facing tool surface = 8 read + 5 mutating = 13 tools. Other agents have full operational surface on Kora.

2 new client listeners (slack_client_listener + purelymail_client_listener) + 2 new MCP tools (kora__send_slack_dm + kora__send_email) + Slack handler accessor refactor + JSONL caller_actor_kind + PurelymailClient send_email log + listener package wire-in.

§5 rulings: Q1 listener compat (handler accessor first, lazy fallback) / Q2 single JSONL with optional caller_actor_kind / Q3 D-prefix OR Joshua user-ID; U... C... rejected.

Security: tokens absent from error envelopes after diverse-failure-mode injection.

Listener fail-soft contract: capabilities not gates.

36 new tests + 341 cross-bucket regression. CC#1 rebased onto #131 + #132 — combined reasoning-meta JSONL fields with caller_actor_kind audit field; preserved both prose docstrings + entry-building branches. Used explicit-SHA --force-with-lease per feedback_pm_merge_then_delete_race memory.
rafe-walker added a commit that referenced this pull request May 23, 2026
…email outbound log (#146)

Unblocks CC#2 KR-REASONING-PANEL-EMAIL-XREF. Mirrors PR #131 slack_dm pattern for email.

- purelymail_client.py: send_email + _append_outbound_log + send_email_internal each gain 6 optional kwargs.
- email_inbound_handler.py: _call_reasoning_engine returns tuple now; 3 new helpers + dispatch helper; _send_auto_reply threads meta + caller_session_id.

12 new tests (5 client JSONL shape + 7 handler meta threading).

caller_session_id format pinned: email:{message_id} matches engine _derive_caller_session_id. Test imports engine private helper + asserts shape match.

Backwards-compat: non-reasoning callers (MCP kora__send_email, notifications, operator scripts) leave 6 kwargs unset → JSONL unchanged.

SMTP-failure-with-meta tested: reasoning ran but SMTP rejected — JSONL records meta + failed send_status (operator triages).

12 new + 80 existing + 505/505 cross-bucket regression.
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