fix: prompt + query verbs (Ephemeral Sessions) #65

Merged
weiwen merged 1 commit from issue-58 into main 2026-07-12 00:25:36 +08:00
Owner

Closes #58 (parent #55; builds on the #57 dispatch core). Adds the two pi-backed Verbs on top of the control plane, both served from throwaway Ephemeral Sessions so they never abort, block, or evict a
chat's live conversation Slot.

What changes

Ephemeral Session support (src/session.rs)

  • SessionManager::run_ephemeral(chat_id, message) — spawns a fresh pi process for a single turn, returns the AgentResponse, and kills the process regardless of outcome. It never touches the
    sessions map: no get_or_create_slot, no abort registration, no eviction, so an in-flight live turn is neither interrupted nor polluted (the documented context-blindness tradeoff).
  • spawn_ephemeral / run_ephemeral_turn helpers. The turn holds its abort sender for the duration so the receiver stays pending and the read path always wins — ephemeral turns are never aborted.
  • set_ephemeral_factory (test-only, behind #[cfg(test)]) injects a PiProcess factory so ephemeral turns run against PiProcess::fake_responder without a real pi binary; set before the Arc is
    shared.

Control plane verbs (src/control_plane.rs)

  • New ControlRequest::Prompt and ControlRequest::Query variants (tagged verb, snake_case), plus a response field on ControlResponse populated only by query.
  • Deliverer trait (async-trait) abstracts Telegram delivery: BotDeliverer for production, a recording double for tests. ControlPlane::new now takes Option<Arc<SessionManager>>; the old bot: Option<Bot> field is replaced by deliverer: Option<Box<dyn Deliverer>>. A new_with_deliverer test constructor injects the recorder.
  • handle_prompt: resolves the chat, runs an ephemeral turn, delivers the response text to the resolved Telegram chat; rejects api- targets (no asynchronous delivery path for HTTP clients).
  • handle_query: resolves the chat, runs an ephemeral turn, returns the response text to the caller and delivers nothing. Any resolved chat is accepted (api- included), since the ID only names the
    session. Immediate-only — no Schedule/deferral wiring.

CLI (src/main.rs)

  • Prompt and Query clap subcommands with the same --chat → $EVIE_CHAT_ID → daemon-resolved targeting as send.
  • send/prompt/query dispatch refactored into a shared run_cli_verb helper: prints the query response text to stdout, prints ok for delivery verbs, exits non-zero on error. resolve_cli_chat
    extracts the shared client-side fallback. The daemon constructs ControlPlane with the live SessionManager.

Deps: async-trait added to Cargo.toml/Cargo.lock.

Tests (all at the dispatch-core seam via PiProcess::fake_responder)

  • run_ephemeral_returns_response / run_ephemeral_isolated_from_live_slot — the latter inserts a live slot with an active turn via insert_slot_for_test, runs the ephemeral turn, and asserts the live
    slot is still present, still Arc::ptr_eq to the original, and its in-flight turn was not aborted.
  • query_returns_ephemeral_response, prompt_delivers_ephemeral_response_to_chat (verifies exactly one delivery to the resolved chat with the ephemeral response text, and response absent on the
    prompt reply), prompt_rejects_api_chat_but_query_accepts_it, plus serde round-trips for the new variants and ControlResponse::with_response.

Full suite: 154 passed, 0 failed. Vocabulary and the Ephemeral-Session isolation contract follow CONTEXT.md and ADR 0006.

Closes #58

Closes #58 (parent #55; builds on the #57 dispatch core). Adds the two pi-backed Verbs on top of the control plane, both served from throwaway Ephemeral Sessions so they never abort, block, or evict a chat's live conversation Slot. ### What changes **Ephemeral Session support (`src/session.rs`)** - `SessionManager::run_ephemeral(chat_id, message)` — spawns a fresh `pi` process for a single turn, returns the `AgentResponse`, and kills the process regardless of outcome. It never touches the `sessions` map: no `get_or_create_slot`, no abort registration, no eviction, so an in-flight live turn is neither interrupted nor polluted (the documented context-blindness tradeoff). - `spawn_ephemeral` / `run_ephemeral_turn` helpers. The turn holds its abort sender for the duration so the receiver stays pending and the read path always wins — ephemeral turns are never aborted. - `set_ephemeral_factory` (test-only, behind `#[cfg(test)]`) injects a `PiProcess` factory so ephemeral turns run against `PiProcess::fake_responder` without a real `pi` binary; set before the `Arc` is shared. **Control plane verbs (`src/control_plane.rs`)** - New `ControlRequest::Prompt` and `ControlRequest::Query` variants (tagged `verb`, snake_case), plus a `response` field on `ControlResponse` populated only by `query`. - `Deliverer` trait (`async-trait`) abstracts Telegram delivery: `BotDeliverer` for production, a recording double for tests. `ControlPlane::new` now takes `Option<Arc<SessionManager>>`; the old `bot: Option<Bot>` field is replaced by `deliverer: Option<Box<dyn Deliverer>>`. A `new_with_deliverer` test constructor injects the recorder. - `handle_prompt`: resolves the chat, runs an ephemeral turn, delivers the response text to the resolved Telegram chat; rejects `api-` targets (no asynchronous delivery path for HTTP clients). - `handle_query`: resolves the chat, runs an ephemeral turn, returns the response text to the caller and delivers nothing. Any resolved chat is accepted (`api-` included), since the ID only names the session. Immediate-only — no Schedule/deferral wiring. **CLI (`src/main.rs`)** - `Prompt` and `Query` clap subcommands with the same `--chat → $EVIE_CHAT_ID → daemon-resolved` targeting as `send`. - `send`/`prompt`/`query` dispatch refactored into a shared `run_cli_verb` helper: prints the `query` response text to stdout, prints `ok` for delivery verbs, exits non-zero on error. `resolve_cli_chat` extracts the shared client-side fallback. The daemon constructs `ControlPlane` with the live `SessionManager`. **Deps**: `async-trait` added to `Cargo.toml`/`Cargo.lock`. ### Tests (all at the dispatch-core seam via `PiProcess::fake_responder`) - `run_ephemeral_returns_response` / `run_ephemeral_isolated_from_live_slot` — the latter inserts a live slot with an active turn via `insert_slot_for_test`, runs the ephemeral turn, and asserts the live slot is still present, still `Arc::ptr_eq` to the original, and its in-flight turn was not aborted. - `query_returns_ephemeral_response`, `prompt_delivers_ephemeral_response_to_chat` (verifies exactly one delivery to the resolved chat with the ephemeral response text, and `response` absent on the prompt reply), `prompt_rejects_api_chat_but_query_accepts_it`, plus serde round-trips for the new variants and `ControlResponse::with_response`. Full suite: 154 passed, 0 failed. Vocabulary and the Ephemeral-Session isolation contract follow CONTEXT.md and ADR 0006. Closes #58
feat(control-plane): prompt + query verbs (Ephemeral Sessions) (#58)
All checks were successful
CI / check (pull_request) Successful in 1m40s
PR Triage — label changes-requested reviews / triage-review (pull_request) Successful in 1s
c2cb1a93a4
weiwen merged commit acd7edfe4c into main 2026-07-12 00:25:36 +08:00
weiwen deleted branch issue-58 2026-07-12 00:25:37 +08:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
weiwen/evie!65
No description provided.