feat(continuum-core/persona): L0-2-prep — PersonaSlot extension, enroll opens#1464
Merged
Conversation
…cognition path Joel 2026-05-29: 'would take careful planning to migrate. I would get e2e persona cognition first, within RUST alone.' Plan covers: - What 'e2e persona cognition in Rust alone' means concretely (the cognition decisions + state stay Rust; ingress/egress can stay transitional TS) - Audit of what already runs in Rust (PersonaCognition, PersonaCognitionEngine, full_evaluate, respond, service_cycle, PersonaServiceModule L0-1 minimum) - Audit of what still runs in TS (PersonaAutonomousLoop driving the loop today, PersonaMessageEvaluator orchestrating, etc.) - Five sub-slices: - L0-2-prep: PersonaSlot extension + open enroll (no dispatch) - L0-2-dispatch: service_once_for wired, exercised in tests only - L0-2-cutover: atomic TS-loop deletion + Rust-loop activation - L0-3: genome paging moves to Rust - L0-4: inbox routing moves to Rust - L0-5: final PersonaUser.ts cull - Dependencies + blockers explicitly: NOT blocked by airc#1075 or e51ab14e (uses universal CommandExecutor's existing TS-route branch); BLOCKED by knowing the rag_engine source — open question to investigate before L0-2-prep code Pre-implementation investigation (4 items) called out so the next PR after this is on solid ground. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ll opens Builds on L0-1's minimum unit (#1457). Each enrolled persona gets a PersonaSlot carrying its PersonaCognition (the per-persona container for engine + inbox + rate_limiter + sleep_state + adapter_registry + genome + classifier + caches + admission state from persona::unified). What changes: - PersonaSlot struct (persona_id, display_name, cognition, circuit_open_until_ms, consecutive_failures) - PersonaServiceModule now carries personas: Mutex<HashMap<Uuid, PersonaSlot>> + rag_engine: Arc<RagEngine> (held at module level so all enrolled personas share retrieval substrate) - enroll(persona_id, display_name) — constructs PersonaCognition under the shared RagEngine, stores the slot. Idempotent on persona_id (updates display_name; preserves existing cognition + circuit-breaker state — silently resetting cognition would be a fallback) - persona/status now reports the enrolled list (snapshot of id + display_name + total count) instead of the L0-1 zero stub - persona/enroll command (was: returns L0-2-not-wired error). Parses persona_id (uuid) + display_name from JSON params, calls enroll(), reports the new total - Loud validation: missing persona_id, missing display_name, malformed uuid all fail with named errors. No silent defaults. What does NOT change: - tick is still a no-op. The TS PersonaAutonomousLoop continues to drive the production loop. service_once_for + dispatch wiring lands in L0-2-dispatch. - No TS deleted yet. PersonaAutonomousLoop.ts deletion lands in L0-2-cutover after dispatch is proven. Why this is safe to ship alone: The Rust enrollment is *latent* — enrolling a persona changes no production behavior because the production loop still runs TS-side. When L0-2-dispatch wires service_once_for, the slot machinery is already proven by the L0-2-prep tests. Tests: 10 passing. - config_declares_persona_prefix_and_high_priority - status_with_no_enrollments_reports_zero_and_prep_scope - enroll_constructs_slot_and_status_reflects_it - enroll_is_idempotent_and_updates_display_name - enroll_two_distinct_personas_keeps_both - enroll_missing_persona_id_fails_loud - enroll_missing_display_name_fails_loud - enroll_invalid_uuid_fails_loud - unknown_command_returns_clear_error - tick_is_no_op_in_prep_slice Verified on Xcode 26.3 + llama/metal feature. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 29, 2026
joelteply
added a commit
that referenced
this pull request
May 29, 2026
…h full_evaluate (#1465) 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.
First implementation slice of L0 per L0-PERSONA-COGNITION-E2E-PLAN.md. Builds on L0-1's minimum unit (#1457).
What
PersonaServiceModulenow carries persona slots. Each enrolled persona has aPersonaSlotholding itsPersonaCognition(the per-persona container for engine + inbox + rate_limiter + sleep_state + adapter_registry + genome + classifier + caches + admission state frompersona::unified).PersonaSlotstruct: persona_id, display_name, cognition, circuit_open_until_ms, consecutive_failuresPersonaServiceModule:personas: Mutex<HashMap<Uuid, PersonaSlot>>+ sharedArc<RagEngine>(so all enrolled personas share the retrieval substrate)enroll(persona_id, display_name)— constructs cognition under shared RagEngine, stores slot. Idempotent on persona_id; updates display_name but preserves existing cognition + circuit-breaker state (silently resetting cognition would be a fallback)persona/statusnow reports the enrolled list (id + display_name + total)persona/enrollcommand opens (was: L0-2-not-wired error). Loud validation: missing persona_id, missing display_name, malformed uuid all fail with named errorsWhat does NOT change
tickis still a no-op. The TS PersonaAutonomousLoop continues to drive the production loop. service_once_for + dispatch wiring lands in L0-2-dispatch.Why safe to ship alone
The Rust enrollment is latent — enrolling a persona changes no production behavior because the production loop still runs TS-side. When L0-2-dispatch wires service_once_for, the slot machinery is already proven by these tests.
Tests — 10 passing
Verified on Xcode 26.3 + llama/metal feature.
🤖 Generated with Claude Code