Skip to content

feat(consciousness): core actor loop for event-driven sessions (BRO-455)#49

Merged
broomva merged 2 commits intomainfrom
feature/bro-455-consciousness-actor-loop
Apr 5, 2026
Merged

feat(consciousness): core actor loop for event-driven sessions (BRO-455)#49
broomva merged 2 commits intomainfrom
feature/bro-455-consciousness-actor-loop

Conversation

@broomva
Copy link
Copy Markdown
Owner

@broomva broomva commented Apr 5, 2026

Summary

  • Implement per-session consciousness actors with tokio::select! event loops that replace the blocking run_session handler when ARCAN_CONSCIOUSNESS=true
  • Add ConsciousnessRegistry mapping session IDs to actor handles, with get_or_create, get, and shutdown_all
  • Fix and export arcan-core::queue module (was private/uncompiled) — MessageQueue with steering semantics (Collect/Steer/Followup/Interrupt)
  • Feature-flagged: existing blocking behavior is unchanged when env var is not set

New files

  • crates/arcand/src/consciousness.rs — types, actor, registry, handle (~640 lines)
  • crates/arcand/tests/consciousness_test.rs — 5 integration tests
  • docs/superpowers/plans/2026-04-04-consciousness-actor-loop.md — implementation plan

Modified files

  • crates/arcand/src/canonical.rs — consciousness dispatch in run_session, registry in CanonicalState
  • crates/arcan-core/src/queue.rs — fix compilation bugs (return types, trait impl)
  • crates/arcan-core/src/lib.rs — export pub mod queue

Test plan

  • 5 integration tests: shutdown, idle message acceptance, registry create/retrieve, shutdown_all, channel close
  • 15 unit tests for MessageQueue (queue.rs)
  • Full workspace: 1085 tests passing, 0 failures
  • cargo clippy --workspace — zero warnings
  • cargo build --workspace — clean build
  • Manual test: ARCAN_CONSCIOUSNESS=true cargo run -p arcan + curl POST /runs

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Consciousness mode: per-session, event-driven actor system for managed agent runs with configurable heartbeat, idle timeouts, and iteration limits.
  • Improvements

    • Queue operations now surface errors consistently, improving robustness around enqueueing, draining, preemption checks, and status/health reporting.
  • Tests

    • New integration tests validating actor lifecycle, message handling, registry behavior, and shutdown semantics.
  • Documentation

    • Added implementation plan and design notes for the consciousness actor loop.

Add event-driven per-session consciousness actors that replace the
blocking request-response pattern in run_session. Each session gets a
long-lived tokio::spawn task with a select! event loop that handles
concurrent message queuing, preemption at tool boundaries, and mode
transitions (Idle → Active → Sleeping → ShuttingDown).

Key components:
- ConsciousnessEvent enum (UserMessage, TimerTick, Shutdown)
- SessionConsciousness actor with run_agent_cycle using tick_on_branch
- ConsciousnessRegistry mapping session IDs to actor handles
- ConsciousnessHandle with send/shutdown/is_alive
- Feature flag: ARCAN_CONSCIOUSNESS=true enables consciousness mode
- Queue integration: MessageQueue from arcan-core with steering semantics

Also fixes: export arcan-core queue module (was private/uncompiled),
fix queue.rs compilation errors (drain_after_run return type, trait impl).

5 integration tests + 15 queue unit tests passing.
1085 workspace tests green, 0 failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@linear
Copy link
Copy Markdown

linear bot commented Apr 5, 2026

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a759360b-3bc5-452b-95f7-d004dfd17bd1

📥 Commits

Reviewing files that changed from the base of the PR and between 3b2dc6e and e772870.

📒 Files selected for processing (1)
  • crates/arcan-tui/src/app.rs
✅ Files skipped from review due to trivial changes (1)
  • crates/arcan-tui/src/app.rs

📝 Walkthrough

Walkthrough

Adds a new per-session "consciousness" Tokio actor system gated by an env flag, exports a core queue module, refactors queue APIs to return Result types, integrates the actor into run_session with timeout/ack semantics, adds tests and docs, and introduces a futures-util dependency.

Changes

Cohort / File(s) Summary
Module Exports
crates/arcan-core/src/lib.rs, crates/arcand/src/lib.rs
Exported new public modules: queue and consciousness.
Queue Implementation & API
crates/arcan-core/src/queue.rs
Reformatted lock acquisition chains across many methods; changed PreemptionCheck::check_preemption to return Result<SteeringAction, QueueError>; adjusted drain_after_run to return Ok(Vec::new()) on empty; tests updated to unwrap Results.
Consciousness Actor Module
crates/arcand/src/consciousness.rs
New large module implementing event-driven per-session actor, including ConsciousnessConfig, ConsciousnessEvent, SessionConsciousness, ConsciousnessHandle, and ConsciousnessRegistry with spawn, send, shutdown, heartbeat, idle-sleeping, queue handling, and preemption-aware agent cycles.
Session Handler Integration
crates/arcand/src/canonical.rs
Added optional consciousness_registry to CanonicalState; run_session now conditionally routes requests to the consciousness actor when enabled, sending UserMessage events and awaiting ack with a 5s timeout; returns early on ack outcomes (Accepted, Rejected) or errors (500/504).
Dependency
crates/arcand/Cargo.toml
Added workspace-managed dependency futures-util.
Tests
crates/arcand/tests/consciousness_test.rs
New integration tests (5 async cases) covering actor lifecycle, user message ack behavior, registry creation/retrieval, shutdown_all, and channel-drop stopping.
Docs / Plan
docs/superpowers/plans/2026-04-04-consciousness-actor-loop.md
Added implementation plan/design doc describing the consciousness actor loop, registry, and integration points.
TUI minor change
crates/arcan-tui/src/app.rs
Simplified control flow in App::show_status using if let Ok(...) instead of match when fetching autonomic info.

Sequence Diagram

sequenceDiagram
    participant Client
    participant CanonicalHandler as Canonical Handler
    participant ConsRegistry as Consciousness Registry
    participant SessionActor as Session Consciousness Actor
    participant KernelRT as Kernel Runtime
    participant MsgQueue as Message Queue

    Client->>CanonicalHandler: UserMessage Request
    alt Consciousness Enabled
        CanonicalHandler->>ConsRegistry: get_or_create(session_id, branch, runtime)
        ConsRegistry-->>CanonicalHandler: ConsciousnessHandle
        CanonicalHandler->>SessionActor: send(UserMessageEvent + ack)
        activate SessionActor
        SessionActor->>KernelRT: tick_on_branch/run agent cycle
        KernelRT->>MsgQueue: check_preemption()
        MsgQueue-->>KernelRT: Result<SteeringAction, QueueError>
        KernelRT-->>SessionActor: cycle result
        SessionActor->>CanonicalHandler: send(ConsciousnessAck)
        deactivate SessionActor
        CanonicalHandler-->>Client: HTTP Response (Accepted/Rejected or error)
    else Consciousness Disabled
        CanonicalHandler->>KernelRT: direct tick_on_branch execution
        KernelRT-->>CanonicalHandler: Standard response
        CanonicalHandler-->>Client: HTTP Response
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hop a loop in eventful night,
Spawn sessions, queue the user’s light—
Ack or reject, a timely beat,
Kernel ticks and messages meet.
Wrapped results and actors hum delight.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(consciousness): core actor loop for event-driven sessions (BRO-455)' accurately summarizes the primary change: implementing a new consciousness actor loop for event-driven session handling, and includes the ticket reference.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/bro-455-consciousness-actor-loop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
docs/superpowers/plans/2026-04-04-consciousness-actor-loop.md (1)

15-23: Add language specifier to fenced code block.

The dependency chain diagram should specify a language (or use text/plain for non-code diagrams) to satisfy markdown linting rules.

-```
+```text
 aios-protocol (EventKind, SteeringMode, OperatingMode) — READ ONLY
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/plans/2026-04-04-consciousness-actor-loop.md` around lines
15 - 23, The fenced code block that contains the dependency chain diagram is
missing a language specifier, which triggers markdown linting; update the block
delimiter from ``` to ```text (or ```plain) so the diagram is treated as plain
text, e.g., change the opening fence for the dependency chain diagram to ```text
to satisfy linters while leaving the diagram content unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/arcand/src/canonical.rs`:
- Around line 1632-1638: The RunResponse currently returns events_emitted: 0
which breaks callers that expect a positive event count; update the return in
crates/arcand/src/canonical.rs (the RunResponse construction) to use a sentinel
for async/consciousness mode (e.g., events_emitted: u64::MAX) or compute the
actual count before responding, and remove the redundant explicit mode
assignment if both branches always set OperatingMode::Execute (i.e., adjust the
RunResponse fields: session_id, mode (only set where meaningful), state:
AgentStateVector::default(), events_emitted: <sentinel or computed>,
last_sequence). Ensure the sentinel choice is documented in comments or tests so
clients know how to interpret it.

In `@crates/arcand/src/consciousness.rs`:
- Around line 454-462: Queued messages are currently processed with
RunContext::default() in the drain loop (see drained iteration and
run_agent_cycle call), which discards the original RunContext (system_prompt,
allowed_tools, proposed_tool); modify the queue to carry the original RunContext
(e.g., add a run_context: RunContext field to QueuedMessage or persist it on the
consciousness state when enqueueing) and then pass that stored RunContext into
self.run_agent_cycle(...) instead of RunContext::default(); ensure enqueue logic
that creates QueuedMessage captures the current RunContext and the drain loop
sets self.state.mode = ConsciousnessMode::Active and invokes
run_agent_cycle(msg.content, self.state.branch.clone(), msg.run_context) (or
equivalent using the preserved context).

---

Nitpick comments:
In `@docs/superpowers/plans/2026-04-04-consciousness-actor-loop.md`:
- Around line 15-23: The fenced code block that contains the dependency chain
diagram is missing a language specifier, which triggers markdown linting; update
the block delimiter from ``` to ```text (or ```plain) so the diagram is treated
as plain text, e.g., change the opening fence for the dependency chain diagram
to ```text to satisfy linters while leaving the diagram content unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2dc4879b-408b-4aa4-9a81-f00b0082088e

📥 Commits

Reviewing files that changed from the base of the PR and between e3bf30b and 3b2dc6e.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • crates/arcan-core/src/lib.rs
  • crates/arcan-core/src/queue.rs
  • crates/arcand/Cargo.toml
  • crates/arcand/src/canonical.rs
  • crates/arcand/src/consciousness.rs
  • crates/arcand/src/lib.rs
  • crates/arcand/tests/consciousness_test.rs
  • docs/superpowers/plans/2026-04-04-consciousness-actor-loop.md

Comment on lines +1632 to +1638
return Ok(Json(RunResponse {
session_id,
mode: OperatingMode::Execute,
state: AgentStateVector::default(),
events_emitted: 0,
last_sequence: 0,
}));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

events_emitted: 0 breaks API contract for clients expecting event counts.

The relevant context snippet from crates/arcan/tests/praxis_integration.rs:238-240 shows tests assert events_emitted > 0. When consciousness mode is enabled, this response always returns 0, which could break client integrations that rely on this field to confirm work was done.

Consider either:

  1. Documenting this as expected behavior for async/consciousness mode
  2. Returning a sentinel value (e.g., u64::MAX) to indicate "async processing"
  3. Waiting briefly for actual event count before responding

Additionally, the mode assignment is redundant—both branches return OperatingMode::Execute:

 return Ok(Json(RunResponse {
     session_id,
-    mode: if queued {
-        OperatingMode::Execute
-    } else {
-        OperatingMode::Execute
-    },
+    mode: OperatingMode::Execute,
     state: AgentStateVector::default(),
     events_emitted: 0,
     last_sequence: 0,
 }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/arcand/src/canonical.rs` around lines 1632 - 1638, The RunResponse
currently returns events_emitted: 0 which breaks callers that expect a positive
event count; update the return in crates/arcand/src/canonical.rs (the
RunResponse construction) to use a sentinel for async/consciousness mode (e.g.,
events_emitted: u64::MAX) or compute the actual count before responding, and
remove the redundant explicit mode assignment if both branches always set
OperatingMode::Execute (i.e., adjust the RunResponse fields: session_id, mode
(only set where meaningful), state: AgentStateVector::default(), events_emitted:
<sentinel or computed>, last_sequence). Ensure the sentinel choice is documented
in comments or tests so clients know how to interpret it.

Comment on lines +454 to +462
for msg in drained {
self.state.mode = ConsciousnessMode::Active;
self.run_agent_cycle(
msg.content,
self.state.branch.clone(),
RunContext::default(),
)
.await;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Queued messages lose original RunContext during drain.

When processing drained messages, RunContext::default() is used, which means queued messages won't inherit the original system_prompt, allowed_tools, or proposed_tool. This could cause behavioral differences between immediate execution and queued execution.

Consider storing the RunContext in QueuedMessage or the consciousness state to preserve it:

 for msg in drained {
     self.state.mode = ConsciousnessMode::Active;
     self.run_agent_cycle(
         msg.content,
         self.state.branch.clone(),
-        RunContext::default(),
+        // TODO: Preserve original RunContext for queued messages
+        // For now, use default which loses system_prompt/allowed_tools
+        RunContext::default(),
     )
     .await;
 }

Would you like me to propose a design for preserving RunContext through the queue, or open an issue to track this as a follow-up enhancement?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/arcand/src/consciousness.rs` around lines 454 - 462, Queued messages
are currently processed with RunContext::default() in the drain loop (see
drained iteration and run_agent_cycle call), which discards the original
RunContext (system_prompt, allowed_tools, proposed_tool); modify the queue to
carry the original RunContext (e.g., add a run_context: RunContext field to
QueuedMessage or persist it on the consciousness state when enqueueing) and then
pass that stored RunContext into self.run_agent_cycle(...) instead of
RunContext::default(); ensure enqueue logic that creates QueuedMessage captures
the current RunContext and the drain loop sets self.state.mode =
ConsciousnessMode::Active and invokes run_agent_cycle(msg.content,
self.state.branch.clone(), msg.run_context) (or equivalent using the preserved
context).

Pre-existing lint triggered by CI's newer Rust toolchain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@broomva broomva merged commit 6b8b01e into main Apr 5, 2026
11 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant