Skip to content

feat(persona): Lane D — Rust persona/turn-execute chains drain -> prompt -> inference (#1409)#1415

Merged
joelteply merged 2 commits into
canaryfrom
feat/lane-d-persona-turn-execute
May 18, 2026
Merged

feat(persona): Lane D — Rust persona/turn-execute chains drain -> prompt -> inference (#1409)#1415
joelteply merged 2 commits into
canaryfrom
feat/lane-d-persona-turn-execute

Conversation

@joelteply
Copy link
Copy Markdown
Contributor

Lane D atomic slice — closes alpha card #1409

Adds the persona/turn-execute command in CognitionModule that executes a full persona turn in one Rust hop:

drain inbox
  -> wrap in PersonaTurnFrame
  -> derive ResponsePrompt (lazy output)
  -> build InferenceRequest (prompt_text path)
  -> dispatch \`inference/llm/request\` via command_executor
  -> bundle replayRecord + inferenceResponse
  -> persist replay record (v2 schema with response_prompt captured from #1412)

What changed

  • src/persona/turn_frame.rs: new ResponsePrompt::to_prompt_text helper that flattens system_prompt + chat messages into a single deterministic plain-text prompt for adapter-based engines. Format: \"<system>\\n\\nrole: content\\nrole: content\\n...\". Empty system_prompt produces no leading paragraph; lowercase role matches PromptRole serde format.
  • src/modules/cognition.rs: new persona/turn-execute command. Inputs: persona_id (required), window_ms (default 80), max_items (default 16), composition_artifact_id (default Uuid::nil()), max_tokens (default 512), max_duration_ms (default 10_000). Returns { replayRecord, inferenceResponse }. Empty drain returns the null pair (no-op, not Err). Missing persona returns typed Err per never-swallow rule.

Tests

+9 new tests, all green:

persona::turn_frame (6 new, 18 total):

  • to_prompt_text_renders_each_message_as_role_colon_content
  • to_prompt_text_prepends_system_prompt_when_present
  • to_prompt_text_skips_empty_system_prompt
  • to_prompt_text_handles_mixed_roles_in_order
  • to_prompt_text_handles_no_messages
  • to_prompt_text_empty_prompt_returns_empty_string

modules::cognition::turn_execute_tests (3 new):

  • turn_execute_persona_not_found_returns_typed_error
  • turn_execute_empty_drain_returns_null_bundle
  • turn_execute_bad_max_items_returns_typed_error

The dispatch-success path runs through command_executor::executor() which is only initialized at runtime startup (ipc/mod.rs). End-to-end dispatch tests live in the integration suite; unit-tests here cover param-parse + short-circuit + persona-not-found paths.

Why this matters

The TS persona loop previously executed each stage with its own IPC round-trip (drain → build prompt → call inference), 3 round-trips per turn, with prompt-building in TS. Lane D pulls all three into the substrate so:

  1. Prompt built in Rust where the turn-frame lives
  2. Production replay record carries the exact prompt that fed inference (v2 schema from feat(persona): Lane D — bump turn-frame replay record to v2 with response_prompt #1412)
  3. Persona turn becomes one observable unit on the bus

Validation command

cd src/workers/continuum-core && cargo test --features metal,accelerate --lib persona::turn_frame turn_execute

Result locally: 21/21 pass (18 turn_frame + 3 turn_execute).

Stack

Builds atop:

Follow-up work (not in scope):

  • Integration test booting Runtime with InferenceLlmModule registered + asserting end-to-end dispatch
  • TS callsite migration to use `persona/turn-execute` instead of the 3-call chain

🤖 Generated with Claude Code

…mpt -> inference (#1409)

Adds the `persona/turn-execute` command in CognitionModule that
executes a full persona turn in ONE Rust hop:

  drain inbox
    -> wrap in PersonaTurnFrame
    -> derive ResponsePrompt (lazy)
    -> build InferenceRequest (prompt_text path)
    -> dispatch `inference/llm/request` via the global
       command_executor (routes to InferenceLlmModule registered
       in PR-5 #1404)
    -> bundle replayRecord + inferenceResponse
    -> persist replay record (v2 schema with response_prompt
       captured from #1412)

Files changed:

* src/persona/turn_frame.rs: new `ResponsePrompt::to_prompt_text`
  helper that flattens system_prompt + chat messages into a single
  deterministic plain-text prompt for adapter-based engines
  (LlamaCppAdapter, cloud adapters). Format:
    "<system>\n\nrole: content\nrole: content\n..."
  Empty system_prompt produces no leading paragraph; lowercase
  role matches the on-the-wire PromptRole serde format.

* src/modules/cognition.rs: new `persona/turn-execute` command.
  Inputs:
    - persona_id (required)
    - window_ms (default 80), max_items (default 16)
    - composition_artifact_id (default Uuid::nil())
    - max_tokens (default 512), max_duration_ms (default 10_000)
  Returns:
    { "replayRecord": PersonaTurnFrameReplayRecord | null,
      "inferenceResponse": InferenceResponse | null }
  Empty drain returns the null pair (no-op, not Err). Missing
  persona returns typed Err per Joel's never-swallow rule.

Tests (+9, all green):

* persona::turn_frame (6 new, total 18):
  - to_prompt_text_renders_each_message_as_role_colon_content
  - to_prompt_text_prepends_system_prompt_when_present
  - to_prompt_text_skips_empty_system_prompt
  - to_prompt_text_handles_mixed_roles_in_order
  - to_prompt_text_handles_no_messages
  - to_prompt_text_empty_prompt_returns_empty_string

* modules::cognition::turn_execute_tests (3 new):
  - turn_execute_persona_not_found_returns_typed_error
  - turn_execute_empty_drain_returns_null_bundle
  - turn_execute_bad_max_items_returns_typed_error

The dispatch-success path (drain -> dispatch -> inference response)
runs through `command_executor::executor()` which is only
initialized at runtime startup (ipc/mod.rs). Tests that exercise
the executor live in the integration suite; unit-tests here cover
the param-parse + short-circuit + persona-not-found paths.

Builds atop #1412 (v2 schema with response_prompt) and #1404
(InferenceLlmModule runtime registration). Closes alpha card
#1409.

Why one command: the TS persona loop previously executed each
stage with its own IPC round-trip (drain, then build prompt,
then call inference) — 3 round-trips per turn, prompt-building
lived in TS. Lane D pulls all three into the substrate so
(a) the prompt is built in Rust where the turn-frame lives,
(b) the production replay record carries the exact prompt that
fed inference, (c) the persona turn becomes one observable unit
on the bus.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(persona,#1409): force turn-execute through Rust registry

* fix(runtime,#1409): use unlimited concurrency contract for cognition

---------

Co-authored-by: Test <test@test.com>
@github-actions github-actions Bot added size: XL and removed size: L labels May 18, 2026
@joelteply joelteply merged commit e58b49f into canary May 18, 2026
3 checks passed
@joelteply joelteply deleted the feat/lane-d-persona-turn-execute branch May 18, 2026 19:17
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