feat(continuum-core/persona): L0-2-dispatch — service_once_for through full_evaluate#1465
Merged
Conversation
…h full_evaluate Builds on L0-2-prep (#1464). Each EnrolledPersona now carries its own ChannelRegistry + PersonaState, and the service module has the dispatch path wired through the unified pre-response gate. Why the slot rename: - L0-2-prep introduced `service_module::PersonaSlot` which collided with the existing `cognition::response_orchestrator::PersonaSlot` (a minimal identity+specialty DTO used as input to respond()). - Renamed mine to `EnrolledPersona` — clearer name AND no collision. What changes: - EnrolledPersona extends with channels: ChannelRegistry + state: PersonaState (initialized fresh in enroll) - service_once_for(persona, now_ms) — pops via channels.service_cycle, deserializes the chat item (local ChatItemWire struct matching the camelCase to_json output), builds a FullEvaluateRequest, calls full_evaluate, returns the decision as ServiceOnceOutcome - drain_all_personas(now_ms) — iterates enrolled personas, calls service_once_for up to MAX_DRAIN_PER_TICK (20) per persona, manages per-persona circuit breaker (5 consecutive failures → 30s cooldown) - tick now calls drain_all_personas - ServiceOnceOutcome enum: Idle | Evaluated{message_id,decision} | UnsupportedItem{item_type} — voice + task items surface as UnsupportedItem rather than silently dropped (anti-fallback) Production safety: - No production code calls persona/enroll yet. The runtime invokes tick() every 250ms but with zero enrolled personas it's a no-op. - L0-2-cutover will atomically (a) wire persona/enroll from production, (b) delete PersonaAutonomousLoop.ts, (c) make Rust the production driver of the loop. What does NOT change yet: - No call to respond() — that needs upstream TurnContext + room history + known-specialties roster that lives in PersonaMessageEvaluator today. Follow-up slice wires respond() with the upstream context plumbed through. - No TS deletions yet. Constants: - CIRCUIT_BREAKER_MAX_CONSECUTIVE_FAILURES: 5 - CIRCUIT_BREAKER_COOLDOWN_MS: 30_000 - MAX_DRAIN_PER_TICK: 20 Tests: 16 passing (10 L0-2-prep + 6 new dispatch tests). - service_once_for_idle_returns_idle - service_once_for_dispatches_chat_item_through_full_evaluate - drain_all_personas_processes_two_personas_independently - drain_respects_max_drain_per_tick - tick_with_no_enrolled_personas_succeeds_quietly - tick_with_enrolled_persona_and_no_items_is_no_op Verified on Xcode 26.3 + llama/metal feature. 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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Second implementation slice of L0-PERSONA-COGNITION-E2E-PLAN.md. Builds on L0-2-prep (#1464).
What
Each
EnrolledPersonanow carries its ownChannelRegistry+PersonaState, and the service module wires the dispatch path through the unified pre-response gate.PersonaSlot→EnrolledPersona(collided with existingcognition::response_orchestrator::PersonaSlotDTO)service_once_for(persona, now_ms)— pops viachannels.service_cycle, deserializes the chat item (localChatItemWirematching the camelCaseto_jsonoutput), builds aFullEvaluateRequest, callsfull_evaluate, returns the decision asServiceOnceOutcomedrain_all_personas(now_ms)— iterates enrolled personas, callsservice_once_forup toMAX_DRAIN_PER_TICK(20) per persona, manages per-persona circuit breaker (5 failures → 30s cooldown)ticknow callsdrain_all_personasServiceOnceOutcomeenum:Idle/Evaluated{message_id, decision}/UnsupportedItem{item_type}— voice + task items surface asUnsupportedItemrather than silently dropped (anti-fallback)Production safety
persona/enrollyet. The runtime invokestick()every 250ms but with zero enrolled personas it's a no-op.persona/enrollfrom production, (b) deletePersonaAutonomousLoop.ts, (c) make Rust the production driver.What does NOT change yet
respond()— that needs upstreamTurnContext+ room history + known-specialties roster (lives inPersonaMessageEvaluatortoday). Follow-up slice wires this with the upstream context plumbed through.Tests — 16 passing (10 prep + 6 new)
Verified on Xcode 26.3 + llama/metal feature.
🤖 Generated with Claude Code