feat(continuum-core/persona): PersonaServiceModule — L0-1 singleton Rust ServiceModule#1457
Merged
Merged
Conversation
e0c903d to
2054b0c
Compare
…erviceModule (L0-1) Replaces TypeScript PersonaAutonomousLoop. ONE Rust tick services every enrolled persona instead of N TS loops crossing the V8↔Rust IPC boundary on every cadence beat. Why singleton, not per-persona: - ModuleConfig.name is &'static str — runtime registry can't store dynamic per-persona names. - Beyond the constraint, singleton wins anyway: one tick = whole fleet, adding the 16th persona is enrollment-only, the cadence budget is shared across personas instead of per-persona contended. Surface: - enroll(persona_id, display_name) -> Result - enrolled_count() -> usize - ServiceModule impl with command_prefixes=["persona/"], High prio, 250ms tick. Handles persona/status + persona/enroll. - Per-persona circuit breaker (5 consecutive failures = 30s cooldown) + per-persona drain bound (20 items / tick) keeps one bad persona from starving the rest. Tests: 8 unit tests covering config, status, enroll/idempotency, multi-persona, unknown-command rejection, empty-tick, enrolled-tick. Note on as_any: ServiceModule trait currently requires it for downcasting in the registry; tracked separately for removal per the no-Any directive. L0-1 of GRID-MIGRATION-ROADMAP (PR #1442 merged into canary). Follow-ups: L0-2 (cognition dispatch in service_once_for), L0-3 (genome manager), L0-4 (inbox routing), L0-5 (delete the TS PersonaAutonomousLoop once L0-1..L0-4 land). Verified on Xcode 26.3 + llama metal feature, all 8 tests pass. No /Users paths, no private deps — all airc crates pinned at workspace level to public CambrianTech/airc git revs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2054b0c to
dfd4e50
Compare
joelteply
added a commit
that referenced
this pull request
May 29, 2026
…ll opens (#1464) * docs(grid): L0 E2E persona cognition plan — sequencing the Rust-only 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> * feat(continuum-core/persona): L0-2-prep — PersonaSlot extension, enroll 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> --------- 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.
Roadmap: L0-1 of GRID-MIGRATION-ROADMAP (#1442). First proof point of the persona-Rust migration end-to-end.
Why
Joel directives 2026-05-29: rust core, nodejs is web only, AI persona under Rust domain, TS personas were CPU-killing. PersonaUser.ts ran every cadence tick through V8 → IPC → Rust → IPC → V8 dispatch; with 15 personas, hundreds of IPC round-trips/sec on the JS event loop. This collapses to ONE Rust tick.
What
Two pieces on one branch:
continuum-core::aircmodule (scaffold)AircHandlewrappingairc_lib::Aircfor in-process embeddingContinuumAdapterimplementingairc_lib::adapter::ConsumerAdapter(from airc PR Add host capability probe so resolver actually runs in production #1075) forforge.continuum.event.v1on_envelope— translation into continuum's command_bus / persona inbox is the follow-upcontinuum-core::persona::service_module(L0-1 done)PersonaServiceModuleowning ALL personas' service cycles in-processpersona/enroll+persona/statuscommandschannel_registry::service_cycle()directly — zero IPC, zero V8 blockingWhy singleton instead of per-persona module
ModuleConfig.name: &'static str— runtime registry can't store dynamic names. Beyond the constraint, singleton wins anyway: one tick = whole fleet, adding the 16th persona is enrollment-only.Verified end-to-end
cargo check -p continuum-core --lib --features llama/metal: clean (Xcode 26.3, llama.cpp submodule initialized)cargo test persona::service_module: 8/8 passingcargo test airc: 3/3 passingPath-dep note (revert before merge)
Cargo.tomltemporarily points the airc-lib path at/Users/joel/.airc/worktrees/9c63f3d8(the #1075 ConsumerAdapter branch). Once airc PR #1075 merges to rust-rewrite, revert to the sibling../../../../airc/crates/airc-libpath before final merge. This is a blocker — do not merge until #1075 + #1081 land in airc rust-rewrite.What L0-1 does NOT yet do (follow-up cards)
service_once_for. Currently acks-and-discards items; needs per-persona PersonaContext (model, system_prompt, capabilities, recent_history) injected at enrollment. Slot extension follows.dyn Anyfrom ServiceModule trait. Currentas_anyimpl is debt per Joel's no-Any directive.Roadmap context
🤖 Generated with Claude Code