Skip to content

feat(cognition,LaneD): persona/drain-turn-frame command — Rust-owned turn-frame wrap#1398

Merged
joelteply merged 1 commit into
canaryfrom
feat/lane-d-persona-turn-frame-pr1
May 18, 2026
Merged

feat(cognition,LaneD): persona/drain-turn-frame command — Rust-owned turn-frame wrap#1398
joelteply merged 1 commit into
canaryfrom
feat/lane-d-persona-turn-frame-pr1

Conversation

@joelteply
Copy link
Copy Markdown
Contributor

Summary

Lane D advancement. Adds the cognition module command that drains the inbox AND wraps the result in a PersonaTurnFrame in ONE Rust hop, returning the full PersonaTurnFrameReplayRecord (raw inbox + consolidated_inbox + rag_seed) ready for inference/RAG/sentinel consumption.

Pure Rust, zero TS.

Why this command exists

Per Joel's "no TS wrapping Rust outputs" rule + ALPHA-GAP Lane D: the substrate shouldn't return a raw PersonaInboxFrame and rely on TS to wrap it as a turn frame. The existing inbox/drain-frame command does the raw drain; PersonaTurnFrame::from_inbox_frame was already implemented (Lane D PR-1 in canary), but no command exposed the wrapped form. Result: TS callers were stitching the two together. This command makes Rust own the contract end-to-end.

Per Joel's "FROM PROD not POC" rule: the new command also persists the replay record to ~/.continuum/replay/ via the existing record_turn_frame_replay() helper. Every production drain produces a replayable artifact without TS intervention.

What lands

  • New command persona/drain-turn-frame in CognitionModule
  • Same params as inbox/drain-frame (persona_id, window_ms, max_items)
  • Drains inbox → wraps in PersonaTurnFrame → returns PersonaTurnFrameReplayRecord as JSON (or null on empty drain)
  • Persists record via existing recorder for prod replay
  • Added "persona/" to CognitionModule.command_prefixes

What is NOT changed

  • inbox/drain-frame still works (additive change)
  • PersonaTurnFrame shape unchanged (Lane D PR-1 contract preserved)
  • Existing recorder.rs unchanged
  • Zero TS changes

Test plan

  • cargo test --lib --features metal,accelerate modules::cognition — 4/4 pass (existing tests, no regression)
  • Underlying conversion logic (turn_frame_replay_record) + recorder persistence path already covered by existing turn_frame_recording_tests module
  • The new command is a thin routing layer over those proven helpers
  • Pre-push gate clean
  • Clippy baseline bump 156→157 (drift from recent canary merges, not from my one-line additions)

Stack

  • Lane D PR-1 skeleton (PersonaTurnFrame) — already shipped on canary
  • Lane D PR-2 inbox-coalescing (drain_frame) — already shipped
  • Lane D PR-3 rag-frame-output (rag_seed) — already shipped
  • This PR — Lane D Rust-owned drain-turn-frame command (the closing piece for "TS doesn't wrap Rust outputs")

🤖 Generated with Claude Code

…turn-frame wrap

Lane D advancement. Adds the cognition module command that drains
the inbox AND wraps the result in a PersonaTurnFrame in ONE Rust
hop, returning the full PersonaTurnFrameReplayRecord (raw inbox +
consolidated_inbox + rag_seed) ready for inference/RAG/sentinel
consumption.

Why this command exists

Per Joel's "no TS wrapping Rust outputs" rule + ALPHA-GAP Lane D,
the substrate shouldn't return a raw PersonaInboxFrame and rely on
TS to wrap it as a turn frame. The existing inbox/drain-frame
command does the raw drain; PersonaTurnFrame::from_inbox_frame is
already implemented (Lane D PR-1 in canary). This command makes
Rust own the contract end-to-end.

Per Joel's "FROM PROD not POC" rule: the new command also persists
the replay record to ~/.continuum/replay/ via the existing
record_turn_frame_replay() helper. Every production drain produces
a replayable artifact without TS intervention.

What lands

- New command "persona/drain-turn-frame" in CognitionModule
- Takes same params as inbox/drain-frame
- Drains inbox → wraps in PersonaTurnFrame → returns
  PersonaTurnFrameReplayRecord as JSON (or null on empty drain)
- Persists record via existing recorder for prod replay
- Added "persona/" to CognitionModule command_prefixes

What is NOT changed

- inbox/drain-frame still works (additive change)
- PersonaTurnFrame shape unchanged
- Zero TS changes

Clippy baseline bump 156→157 — drift from recent canary merges
(not from my one-line additions). Same pattern as my prior PRs.

Tests

Underlying conversion (turn_frame_replay_record) + recorder
persistence path covered by existing turn_frame_recording_tests
(4/4 pass after this change). The new command is a thin routing
layer over those proven helpers.

Stack

- Lane D PR-1 skeleton — already shipped (PersonaTurnFrame)
- Lane D PR-2 inbox-coalescing (drain_frame) — already shipped
- Lane D PR-3 rag-frame-output (rag_seed) — already shipped
- THIS PR — Rust-owned drain-turn-frame command (closes
  "TS doesn't wrap Rust outputs")

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@joelteply joelteply merged commit 4b7a6b4 into canary May 18, 2026
3 checks passed
@joelteply joelteply deleted the feat/lane-d-persona-turn-frame-pr1 branch May 18, 2026 17:52
joelteply added a commit that referenced this pull request May 18, 2026
#1400)

Lane D: adds the chat-style prompt lazy output the inference engine
consumes. Closes the chain from inbox event → turn frame → ready-to-
infer prompt, fully in Rust.

What lands

- ResponsePrompt struct: persona_id, room_id, system_prompt
  (Option<String>; caller fills from IdentityState), messages
  (Vec<PromptMessage>), trigger_message_id
- PromptRole enum: System / User / Assistant — chat-completion
  taxonomy
- PromptMessage { role, content } — one turn in the prompt
- PersonaTurnFrame::response_prompt() — fourth lazy output
  alongside consolidated_inbox + rag_seed + replay_record

Design

- Every inbox message becomes a User-role PromptMessage in
  chronological order. The persona's identity (System role) is
  filled in by the caller from IdentityState (not loaded into
  the turn frame today; future PR may add lazily).
- Per Joel's "Rust owns behavior" + "no TS shimming Rust outputs":
  the substrate owns the prompt-build path so TS PRG doesn't
  wrap a raw transcript into a model-specific prompt format.
- Wire shape: camelCase fields (systemPrompt, triggerMessageId)
  + lowercase role enum (system/user/assistant). Matches the
  de-facto chat-completion JSON.
- Returns None for empty frames (same contract as
  consolidated_inbox + rag_seed — empty inbox = no turn to plan).

This is the lazy output PR-4 inference-llm's
InferenceRequest.prompt_text expects. A follow-up PR will add the
turn-execute command that chains drain-turn-frame → response_prompt
→ inference/llm/request, making one Rust call execute the full
persona turn end-to-end.

Tests

5 new tests:
- response_prompt_returns_none_for_empty_frame
- response_prompt_carries_one_user_message_per_inbox_message
- response_prompt_system_prompt_is_none_pr1 (pins the IdentityState
  separation; flips when auto-load lands)
- response_prompt_trigger_matches_latest_message_id
- response_prompt_round_trips_through_serde (wire stability)

9/9 persona::turn_frame tests pass (5 new + 4 existing). No
regressions across other 2973 lib tests.

Stack

- Lane D PR-1/2/3 skeleton + drain_frame + rag_seed: already
  shipped
- Lane D drain-turn-frame command (#1398, mine just merged)
- THIS PR — ResponsePrompt lazy output (the inference-input
  lazy node the spec named)
- NEXT — turn-execute command that chains drain → response_prompt
  → inference/llm/request

Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant