This repository was archived by the owner on May 26, 2026. It is now read-only.
feat(kora): KR-CHEAP-TRIVIAL-DM-SHORTCIRCUIT — regex + snapshot-interp phrasebook (R3-4 #2)#160
Merged
rafe-walker merged 1 commit intoMay 24, 2026
Conversation
…p phrasebook (R3-4 #2) Per Council R3 Lock R3-4 build-list #2. Pre-filter on Slack DM handler that catches routine status queries ("what's my burn?", "any alerts?") and answers from the pre-warmed snapshot (PR #157) at zero LLM cost. Pairs with prompt caching (#158) — caching halves LLM-bound work; short-circuit eliminates the LLM call entirely for matched patterns. Estimated coverage: 30-40% of operator traffic. # New package kora_cli/short_circuit/ __init__.py dm_phrasebook.py default_slack_dm_phrasebook.yml Public surface (re-exported from __init__): - PhrasebookEntry / ShortCircuitMatch (frozen dataclasses) - load_phrasebook(path=None) — bundled default unless override at ${KORA_HOME}/phrasebook/slack_dm.yml exists; malformed override → WARN + fall back to bundled - match_message(text, phrasebook) — first-match-wins, case- insensitive, strips whitespace - render_reply(entry, snapshot) — walks {snapshot.dotted.path} placeholders; returns None on snapshot None / missing path / None value / literal "unknown" sentinel (PR #157 degraded marker) so we never ship a half-filled response - try_short_circuit(text, phrasebook, snapshot) — convenience wrapper: match → render → ShortCircuitMatch or None # Bundled default phrasebook 8 entries: greeting, thanks, ack, burn_query, status_query, alert_query, health_query, pause_check. Tone target: Kora (direct, brief, conversational) not chatbot phrasing. Operator-editable post-merge via ${KORA_HOME}/phrasebook/slack_dm.yml override (REPLACES bundled — copy the doc as starting point). # Handler wiring (kora_cli/handlers/slack_dm_handler.py) handle_event refactored: short-circuit attempt runs BEFORE engine resolution; on hit, sentinel reasoning_meta is built (model_used="short_circuit", input_tokens=0, output_tokens=0, cache_*_tokens=0, short_circuit_category, short_circuit_ pattern). On miss → falls through to the original engine resolution path (extracted into _resolve_via_engine helper for clarity; existing engine behavior unchanged byte-for-byte). _append_outbound_log_entry extended with two new optional kwargs (short_circuit_category, short_circuit_pattern) — same None-omit semantic as the existing cache_* fields. Flowing through naturally via **reasoning_meta splat. Phrasebook is cached on the handler instance after first call. Any exception during _try_short_circuit (broken phrasebook, import failure, etc.) is logged + swallowed; handler falls through to engine as if no phrasebook existed. DM handling NEVER breaks because of a bad phrasebook. # Cost-ladder no-op for short-circuit hits Short-circuit's sentinel reasoning_meta sets all four token buckets to 0. The existing _record_inference_to_cost_ladder guard (added in #158) bails when every bucket is 0 → no spurious cost-ladder writes. Verified by test_short_circuit_does_not_call_record_inference. # Telemetry contract for CC#1's KR-CHEAP-COST-TELEMETRY Literal "short_circuit" string in model_used is the join key. Don't rename without coordinating. category field lets the reasoning panel break down which query shapes are getting short-circuited (e.g. "burn_query: 12 hits / 24h"). # Package data wiring tool.setuptools.package-data → kora_cli gained "short_circuit/*.yml" so the bundled YAML ships with the wheel. importlib.resources loads it from both source tree + installed wheel; source-tree Path fallback for very-early-boot edge cases. # Tests tests/kora_cli/short_circuit/test_dm_phrasebook.py — 25 tests: - load_phrasebook: bundled, explicit path, operator override, malformed override fallback, invalid regex fallback - match_message: case-insensitive, whitespace strip, first- match-wins, empty/whitespace/non-string returns None, no-match returns None - render_reply: all-fields-present, snapshot None, missing path, "unknown" sentinel, None value, nested dict path, literal punctuation preservation - try_short_circuit: match+render integration, no match, match-but-fall-through (snapshot None / degraded field) - Bundled default sanity: all patterns compile; every entry with placeholders renders against canonical fresh snapshot; fully-degraded snapshot falls through for every placeholder entry (bare-text entries still render) tests/kora_cli/handlers/test_slack_dm_short_circuit.py — 15 tests: - Match bypasses engine; post_dm receives rendered template - Outbound JSONL carries telemetry keys (model_used= short_circuit, short_circuit_category, short_circuit_ pattern, zero token buckets, tools_used=[]) - All 7 categories route correctly (parametrized) - No match → engine called as before; no JSONL contamination - Match + no snapshot → fall through to engine - Match + degraded field → fall through to engine - Broken phrasebook → fall through, handler stays alive - model_used="short_circuit" is the CC#1 join key (literal contract test) - Short-circuit hits don't burn $200/mo pool (record_inference never called) # Regression 532/532 short_circuit + handlers + listeners + reasoning green serially. Full repo xdist: 9319 passed, 43 failed identical to baseline (test_anthropic_adapter / test_backup / test_config / test_gateway_* / test_web_server* / test_kanban_db / test_list_picker_providers / test_model_switch_* / test_startup_plugin_gating / test_web_server_cron_profiles). Zero failures in short_circuit, handlers, or reasoning. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 24, 2026
rafe-walker
pushed a commit
that referenced
this pull request
May 24, 2026
…tion + revert Build-list follow-on to PR #167 (phrasebook read-only viewer). Adds the operator-edit story so the phrasebook can be modified from the cockpit instead of YAML by hand. Also sets the UX foundation that the eventual promotion-review panel will reuse (KR-PROMOTE-PHRASEBOOK proposals are pending edits to this same surface). Backend (kora_cli/short_circuit/phrasebook_editor.py — new) ========================================================== Module hosts everything the read-only viewer didn't need: * validate_entries(entries) → [EntryValidationError, ...] 8 checks (in order; later skipped for an entry that failed earlier to avoid noise): payload is list, length cap (200 entries), required-fields-non-empty, length caps on each field, regex compiles with re.IGNORECASE, catastrophic- backtracking guard (catches (a+)+, (.*)*, etc), snapshot placeholder paths resolve to known scalars, no duplicate (pattern, category) tuples. * SNAPSHOT_SCALAR_PATHS — static frozen-set of known snapshot v4 scalar paths the operator can reference in {snapshot.X.Y} placeholders. Drift-guarded by test_static_schema_matches_snapshot_collectors which greps each leaf key against the snapshot collector source. Only scalars (no dynamic-key dicts like listeners.X or cost_telemetry.X — those str() to Python repr and would render garbage in operator-facing DMs). * write_backup_for / rotate_backups / list_backups — timestamped backups under ${KORA_HOME}/phrasebook/backups/. KORA_PHRASEBOOK_BACKUP_COUNT env var tunes rotation (default 10; clamped to [1, 1000]). Filenames sort chronologically as plain strings (ISO-Z format). * write_phrasebook(entries) — atomic write via utils.atomic_replace (same pattern as snapshot writer). Deterministic field order for readable YAML diffs. * revert_phrasebook(filename=...) — revert to a specific backup OR (no filename) the most-recent OR (no backups) remove the override entirely. Path-traversal defense rejects any filename containing /, \, .., or not matching the slack_dm.*.yml shape. Backend (kora_cli/web_server.py — 3 new endpoints) ================================================== PUT /api/phrasebook/slack_dm — validate + back up + atomic-write + audit POST /api/phrasebook/slack_dm/revert — revert to backup GET /api/phrasebook/slack_dm/backups — list backups PUT contract: * Validation fails → 422 with structured per-entry errors; NO write; previous override preserved. * Validation passes → backup current (if exists) → atomic write → rotate backups → audit row → 200 with echoed entries + backup_filename + rotated_count. * Write itself fails → 500; backup preserved so operator can recover. Audit seam (new): phrasebook.updated ==================================== Extended SeamName Literal in kora_cli/audit/jsonl_sink.py. Each successful PUT (or revert) emits one entry with: * actor — "operator" (cockpit-driven; future "kora_proposal_approved" from the promotion-loop bucket reuses this shape) * action — "put" | "revert" * entry_count_before / entry_count_after * backup_filename (when applicable) * rotated_backup_count (when applicable) * reverted_to (when action=revert) Drives the future KR-PROMOTION-REVIEW-PANEL via the existing audit-panel infrastructure (KR-AUDIT-PANEL-ENDPOINTS PR #155). Frontend ======== * web/src/pages/PhrasebookEditor.tsx (new) — hosts the editor sub-components so PhrasebookPage stays readable: EntryEditorRow (4 inline editable fields + per-field validation errors), EditModeControls (Save / Cancel / Add), BackupsDialog (modal with newest-first list + per-row revert + confirm), ClientSidePreview (mirrors dm_phrasebook.match + render_reply in TS so operator can preview in-progress edits without saving). * api wrappers + types: putSlackDmPhrasebook / revertSlackDmPhrasebook / getSlackDmPhrasebookBackups + PhrasebookEntryWrite / PhrasebookPutResponse / PhrasebookValidationErrorEntry/Body / PhrasebookRevertResponse / PhrasebookBackupItem / PhrasebookBackupsResponse. * PhrasebookPage extended with edit-mode toggle. View-mode surface (read-only table + live tester) is unchanged for operators who just want to inspect. Edit-mode swaps to editor rows + client-side preview + Save/Cancel/Add. Backups button in view-mode opens the revert dialog. * 422 validation-error parsing: Save handler unmarshals fetchJSON's "STATUS: BODY" Error message; on 422 with error="validation_failed" the body's per-entry errors are routed to the editor for inline rendering. Tests ===== Backend (tests/kora_cli/test_phrasebook_editor.py — 44 tests): Validation (14): valid entries, non-list, missing field, empty whitespace, all 4 length caps, entries count cap, invalid regex, catastrophic-backtracking parametrized across 4 pathological patterns, unknown vs known snapshot path, duplicate (pattern, category), schema drift guard against state_snapshot.py. Backups (5): no-override returns None, copies content + timestamps filename, rotation keeps N most recent, env override + clamping parametrized, list returns newest- first with entry_count. Write+revert (5): round-trip load, revert by name, revert most-recent, revert with no backups removes override, path-traversal rejection parametrized across 5 attacks. Endpoints (7): PUT valid + audit, PUT invalid + preserve, PUT no-existing-override, POST revert + audit, POST revert invalid filename → 400, POST revert missing → 404, GET backups newest-first. Audit (1): SeamName Literal includes phrasebook.updated. Integration (1): round-trip PUT then revert restores seed. FE source-pins (tests/kora_cli/test_phrasebook_editor_fe_pins.py — 17 tests): api wrappers (3), TS types declared + shape pinned (2), editor file exists + exports (1), page wiring (3), edit button hidden in edit-mode (1), 422 body parsing branch present (1), ClientSidePreview mirrors backend regex flag + "unknown" sentinel + null-snapshot fall-through (3), SnapshotResponse type covers daemon_health (1). All 76 phrasebook tests pass (44 BE editor + 17 FE pins + 15 existing PR #167 read-side tests). tsc -b clean. vite build clean. Screenshots =========== web/docs/phrasebook-editor/edit_mode.png — edit-mode UI with client-side preview rendering a $0 reply web/docs/phrasebook-editor/validation.png — 422 response with 4 inline per-field errors (bad regex, unknown snapshot path, catastrophic backtracking, missing description) + top-level duplicate error web/docs/phrasebook-editor/revert.png — backups modal with newest-first list, per-row Revert confirm flow, greyed-out corrupt backup Design choices noted (no STOP-ASKs triggered) ============================================= * Snapshot field-path validation uses a STATIC allow-list (SNAPSHOT_SCALAR_PATHS), NOT a live snapshot walk. Spec §4 flagged this as a possible STOP-ASK; the static-list approach is what the spec offered as the alternative ("use SnapshotResponse static schema") and avoids the dynamic-snapshot-during-warm-up problem where freshly- booted daemons would mark canonical paths as invalid. Drift guard test pins each scalar leaf against the snapshot collector source. * Audit seam SeamName extension follows the probe.wake_requested precedent — extend Literal with a new value + add the comment explaining the future actor extension. No consumer drift. * Operator-defined category values: free-form for v1 (the runtime doesn't reserve any category names yet; promotion-loop bucket can add reserved-prefix discipline when its UX lands). Refs ==== * rafe-walker/kora-docs 17_cc_bucket_prompts/KR-FE-PHRASEBOOK-EDITOR-AND-CRUD_write_path_with_validation.md * PR #160 — phrasebook + dm_phrasebook module (read side) * PR #167 — KR-FE-PHRASEBOOK-VIEWER (read-only viewer this extends) * PR #170 — snapshot v4 daemon_health (referenced in SNAPSHOT_SCALAR_PATHS allow-list) 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
Per Council R3 Lock R3-4 build-list #2. Pre-filter on Slack DM handler catches routine status queries ("what's my burn?", "any alerts?") and answers from the pre-warmed snapshot (PR #157) at zero LLM cost. Pairs with prompt caching (#158) — caching halves LLM-bound work; short-circuit eliminates the LLM call entirely for matched patterns.
Operator-review gate: bundled default phrasebook below for Joshua's approval before merge.
Bundled default phrasebook (operator-review focus)
Operator-editable post-merge: drop a replacement YAML at `${KORA_HOME}/phrasebook/slack_dm.yml`. Override REPLACES bundled (not merged) — copy this doc as a starting point. Daemon restart picks up changes; broken override falls back to bundled with a WARN log.
Coverage estimate: 30-40% of Joshua's daily DMs. Highest-volume categories I expect to catch: `ack` (most replies), `thanks`, `status_query`, `alert_query`, `burn_query`. Free-form questions ("walk me through X", "why is Y broken") still hit the engine as intended.
Telemetry hook for CC#1's KR-CHEAP-COST-TELEMETRY
Every short-circuit hit writes `model_used="short_circuit"` into the outbound JSONL plus two new keys: `short_circuit_category` + `short_circuit_pattern`. CC#1's cost-telemetry bucket will:
Implementation
Fall-through semantics (the safety contract)
A phrasebook match returns a real reply ONLY when:
Any failure on (2) or (3) → `_try_short_circuit` returns None → handler falls through to engine resolution unchanged. We never send "your burn is unknown%" to Joshua.
The short-circuit also catches every exception internally → handler stays alive even if the phrasebook YAML is destroyed mid-flight (engine path drives the reply).
Test plan
🤖 Generated with Claude Code