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

KR-INTENT-EMAIL-TO-SEA-TICKET — save ideas to Sea_Ticket from inbox#176

Merged
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-INTENT-EMAIL-TO-SEA-TICKET
May 24, 2026
Merged

KR-INTENT-EMAIL-TO-SEA-TICKET — save ideas to Sea_Ticket from inbox#176
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-INTENT-EMAIL-TO-SEA-TICKET

Conversation

@rafe-walker

Copy link
Copy Markdown
Owner

Summary

Operator R3 Q8a: "I email Kora something I found that I want to save as an idea on a Sea_Ticket." First real product-value use of Kora's inbound-email surface — flips the email path from receive-and-discard (post #173) to receive-recognize-write-confirm.

Joshua emails Kora → identity filter (already gates to KORA_EMAIL_JOSHUA_ADDRESS per the existing handler) → regex intent recognition → sea__create_ticket via in-process IsoKron MCP client → Slack confirmation DM to KORA_SLACK_JOSHUA_USER_ID → audit row on every evaluated email.

Bucket spec: 17_cc_bucket_prompts/KR-INTENT-EMAIL-TO-SEA-TICKET_save_idea_from_inbox.md.

K-DG verdict: no STOP-ASK

The Sea_Ticket write surface IS accessible in-process. mcp_tools.py:_execute_create_sea_ticket (Tool 8 kora__create_sea_ticket) uses get_last_active_provider()._connection.get_mcp_client().invoke("sea__create_ticket", payload). The intent module calls that same path directly (sidesteps the MCP-Tool Caller auth shape — this is operator-driven from the handler, not external-caller-gated).

Regex patterns shipped

All case-insensitive. Highest-confidence match wins; first non-matching tier falls through.

Pattern name Confidence Trigger
subject_idea_prefix high Subject starts with Idea: (then content)
subject_note_prefix high Subject starts with Note:
subject_todo_prefix high Subject starts with TODO:
explicit_save_bracket high [SAVE] literal anywhere in subject or body
explicit_save_phrase high One of save this / save to sea_ticket / save as idea / add to sea / add idea in body or subject
fwd_with_url medium Fwd: / Fw: subject OR Forwarded message body marker AND http:// / https:// / www. URL in body
fwd_without_url medium Same forward shape without a URL
unrecognized n/a Anything else — logged for future promotion-loop training data, no action

Sample DM confirmation text

Success — sent to KORA_SLACK_JOSHUA_USER_ID after a successful write:

:seedling: Got it — saved *Idea: try the new Haiku model* as Sea_Ticket `#STK-42` (matched `subject_idea_prefix`).

Cap-exceeded / failure variant:

:warning: Could not save *Idea: one too many* as Sea_Ticket (matched `subject_idea_prefix`): hourly cap of 10 Sea_Tickets/hour reached; raise KORA_EMAIL_INTENT_HOURLY_CAP or wait

Sample audit entry

{
  "emitted_at": "2026-05-23T19:42:11.512000+00:00",
  "seam": "intent.email_to_sea_ticket",
  "details": {
    "action": "created",
    "pattern_matched": "subject_idea_prefix",
    "confidence": "high",
    "subject": "Idea: try the new Haiku model",
    "ticket_id": "STK-42",
    "tags": ["email", "idea"]
  },
  "caller_session_id": "email:<m-1@example.com>",
  "source": "email"
}

action{created, dry_run, logged_only, cap_exceeded, failed}. The caller_session_id is wire-compatible with the engine-side email:{message_id} derivation so a future xref bucket can join inbound JSONL ↔ audit ↔ Sea_Ticket history by one key.

Env vars added

Env Default Purpose
KORA_EMAIL_INTENT_MIN_CONFIDENCE high Floor for write-through. Set medium to also act on forwards.
KORA_EMAIL_INTENT_DRY_RUN unset (false) Recognize + log + audit but skip MCP write + DM. Tuning aid.
KORA_EMAIL_INTENT_HOURLY_CAP 10 Sliding-window per-hour cap on creates. 0 disables.
(existing) KORA_SLACK_JOSHUA_USER_ID Slack IM channel for confirmation DM. Reused from KR-ALERT-NOTIFY.
(existing) KORA_EMAIL_JOSHUA_ADDRESS Sender identity gate (already enforced by EmailInboundHandler filter 5; this module trusts the upstream check).

Files

  • NEW kora_cli/intent/__init__.py + kora_cli/intent/email_to_sea_ticket.py (~600 lines)
  • NEW tests/kora_cli/intent/__init__.py + tests/kora_cli/intent/test_email_to_sea_ticket.py (34 tests)
  • MOD kora_cli/audit/jsonl_sink.py — added intent.email_to_sea_ticket to SeamName Literal
  • MOD kora_cli/handlers/email_inbound_handler.py — call process_email_intent after _emit_received_event (wrapped in try/except so the intent path can never disturb HANDLED_RECEIVED)

Test plan

  • 34 intent-module tests pass: regex pattern matrix + orchestrator branches (created / dry_run / logged_only / cap_exceeded / failed) + hourly-cap behavior + fail-soft (DM failure doesn't block create; orchestrator swallows unexpected exceptions)
  • Regression: 667 passed (intent + handlers + audit + clients + test_listeners)
  • ruff check clean on all changed files

CC#2 follow-on recommendation: KR-FE-EMAIL-INTENT-LOG-PANEL

The audit-JSONL seam is forensically rich enough today to drive a small cockpit panel without further backend work. Recommended cut:

  1. Read endpoint: /api/audit-events?seam=intent.email_to_sea_ticket — already serviceable by the existing read_audit_entries(seam=...) reader (PR feat(kora): KR-PROBE-AUDIT-AND-CONVERT — cheap-cron + wake-event + fix-envelope per probe #163 era). No new backend code needed.
  2. Panel rows: per audit row, render the subject, pattern_matched, action, and a deep-link to the substrate Sea_Ticket when ticket_id is present.
  3. Filtering: action chip filter (created / dry_run / logged_only / cap_exceeded / failed) is the operator's primary lens — logged_only is the promotion-loop training-data view; failed is the triage view.
  4. Optional sparkline: per-day count of created actions, hosted next to the recent_error_count_5min band in HealthHero post-KR-SNAPSHOT-DAEMON-HEALTH — daemon's own listeners/uptime/errors (schema v4) #170.

The panel can ship before any cockpit-side substrate read — the audit JSONL already carries the operator's full mental model (subject + pattern + outcome).

🤖 Generated with Claude Code

…ickets

Operator R3 Q8a: "I email Kora something I found that I want to
save as an idea on a Sea_Ticket." First real product-value use of
the inbound-email surface (parsing intact since
KR-FEAT-EMAIL-INBOUND-IMAP ST2; auto-reply branch removed in #173
/ Lock R3-8 (a)).

Flow
----
Joshua emails Kora → identity filter (already gates to
KORA_EMAIL_JOSHUA_ADDRESS) → regex intent recognition →
sea__create_ticket via in-process IsoKron MCP client → Slack
confirmation DM to KORA_SLACK_JOSHUA_USER_ID → audit row on every
evaluated email.

New module
----------
`kora_cli/intent/email_to_sea_ticket.py`:
  * `recognize_intent(subject, body, sender) -> EmailIntent` —
    pure regex matcher. No LLM call.
  * `process_email_intent(...)` — orchestrator called from the
    email handler. Always fail-soft (returns action=failed on
    unexpected exception; never propagates).
  * `write_sea_ticket_from_intent(intent)` — direct invocation of
    `sea__create_ticket` via `get_last_active_provider()._connection.get_mcp_client()`
    (sidesteps the MCP-tool Caller-auth surface — this is
    in-process operator-driven, not external-caller-gated).
  * `confirm_via_dm(...)` + `dm_failure(...)` — fail-soft Slack
    confirmation / failure DMs.

Recognition patterns (regex, case-insensitive)
---------------------------------------------
High confidence:
  * `subject_idea_prefix` / `subject_note_prefix` /
    `subject_todo_prefix` — subject starts with `Idea:` / `Note:`
    / `TODO:`
  * `explicit_save_bracket` — `[SAVE]` literal in subject or body
  * `explicit_save_phrase` — `save this` / `save to sea_ticket` /
    `save as idea` / `add to sea` / `add idea` in body or subject

Medium confidence:
  * `fwd_with_url` — `Fwd:` / `Fw:` subject OR `----- Forwarded
    message` body marker AND a URL in body
  * `fwd_without_url` — same forward shape without a URL

Unrecognized → audit logged_only; no MCP call, no DM.

Configuration
-------------
  * `KORA_EMAIL_INTENT_MIN_CONFIDENCE` — `high` (default) or
    `medium`. Floor for write-through.
  * `KORA_EMAIL_INTENT_DRY_RUN` — `true` skips MCP write + DM.
  * `KORA_EMAIL_INTENT_HOURLY_CAP` — int (default 10). Sliding-
    window per-hour cap. 11th match in any 1-hour window is
    audited as `cap_exceeded` + DMs operator. Zero disables.

Audit
-----
New seam `intent.email_to_sea_ticket` (added to SeamName Literal
next to `probe.wake_requested`). One entry per evaluated email
with `action` ∈ {created, dry_run, logged_only, cap_exceeded,
failed}; `caller_session_id` is `email:{message_id}` so
future-bucket cockpit panels can xref to the inbound JSONL.

Handler wire-up
---------------
`EmailInboundHandler._handle_event_inner` now calls
`process_email_intent(...)` after `_emit_received_event`. Wrapped
in try/except so any intent-side failure leaves HANDLED_RECEIVED
intact (belt-and-suspenders — the orchestrator itself is also
fail-soft).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rafe-walker rafe-walker merged commit dc4c0d9 into feature/phase2-upgrades May 24, 2026
@rafe-walker rafe-walker deleted the feat/kora-KR-INTENT-EMAIL-TO-SEA-TICKET branch May 24, 2026 03:58
rafe-walker added a commit that referenced this pull request May 24, 2026
…n + revert (#177)

Operator now edits phrasebook from cockpit. PUT + revert + backups list endpoints (BE) + edit-mode UI in PhrasebookPage (FE). Validation: regex compiles, snapshot-path placeholders, catastrophic-backtracking check, no duplicate (pattern, category). Backup-on-write with env-tunable rotation.

All 3 spec §4 STOP-ASK candidates resolved inline (static-list snapshot-path validation, audit seam Literal extension, free-form category v1).

New audit seam phrasebook.updated with actor field deliberately shaped for future promotion-loop (kora_proposal_approved/pending). Sets UX foundation KR-FE-PROMOTION-REVIEW-PANEL will reuse.

Rebased onto current feature/phase2-upgrades (post #173 / #176) to resolve SeamName Literal adjacent-addition conflict — both intent.email_to_sea_ticket (#176) and phrasebook.updated (#177) preserved in sequence.

76/76 phrasebook tests + tsc + vite build clean.
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