feat(orchestration): subconscious split-brain layer (stages 4-8)#4430
Conversation
…nodes Build the single orchestration wake graph (spec's one StateGraph) as a tinyagents CompiledGraph: OrchestrationState flows through normalize -> frontend -> execute -> frontend -> send_dm -> context_guard -> done, checkpointed under thread orchestration:<session_id>. The frontend (Quick LLM, hint:chat), reasoning core (stubbed this stage), and DM sender are injected as trait objects so graph mechanics are unit-testable with stubs. - graph/state.rs: OrchestrationState + CompressedEntry + WorldDiff (serde). - graph/mod.rs: build/run/topology; command-routing frontend node with a terminate-on-channel_response predicate + max_supersteps backstop. - tools.rs: reply_to_channel + defer_to_orchestrator front-end decision tools. - frontend_agent/: hint:chat built-in package, registered in BUILTINS. - ops.rs: debounced, idempotent invoke_orchestration_graph seeding state from the stage-3 store; production nodes (frontend agent, Signal DM sender). - bus.rs: OrchestrationWakeSubscriber on OrchestrationSessionMessage. - config: debounce_ms / max_supersteps / message_window knobs. Claude-Session: https://claude.ai/code/session_01MjTiUcPjbqXskr9fC1eLKq
…er test - Register orchestration:wake in all_graph_topologies() for debug/inspection. - Add loader test asserting frontend_agent resolves via hint:chat with exactly the two decision tools (Quick-tier verification, stage 4 acceptance). - Extend the non-worker-tier allowlist for the new chat-tier frontend_agent. - rustfmt. Claude-Session: https://claude.ai/code/session_01MjTiUcPjbqXskr9fC1eLKq
Replace the stage-4 execute stub with the real reasoning + memory mechanics of
the unified wake graph. New topology:
normalize -> frontend(1) -> execute -> compress -> world_diff -> frontend(2)
-> send_dm -> context_guard -> done
- Consolidate the graph behind one injected OrchestrationRuntime trait (frontend,
execute, compress, world_diff, context_utilization, evict, send_dm) so the
structure stays hermetically testable with a single stub.
- execute node: reasoning_agent built-in (hint:reasoning) with the per-cycle
subconscious steering directive woven into its system prompt via a task-local;
spawns worker sub-agents. Trace captured for compression.
- compress node (graph/compress.rs): strict 20:1 output budget with a 200-token
floor (only when still compressive), retry-once-then-truncate enforcement, and
an idempotent compressed_history store row per cycle_id.
- world_diff node (graph/world_diff.rs): append-only timeline, monotonic seq from
genesis, terminal_state kv, idempotent by cycle_id.
- context_guard: utilization vs assumed window; at >= context_evict_threshold
(clamped 0.8-0.9) evicts oldest compressed entries to memory RAG under
path_scope orchestration/<session>, resets utilization. Runs after send_dm,
before END.
- OrchestrationState gains cycle_id (deterministic, for resume-idempotent store
writes) + execution_trace. New store tables compressed_history + world_diff.
- reasoning_agent package registered in BUILTINS; config gains
context_evict_threshold + subagent_concurrency.
Tests: compress budget/floor/enforce, world-diff append-only+idempotence, node
ordering (guard-before-END), context-guard threshold 0.84 vs 0.86, steering in
prompt, full-cycle e2e (one DM + one compressed row + one diff entry +
checkpoints). 29 orchestration + 52 loader tests green.
Claude-Session: https://claude.ai/code/session_01MjTiUcPjbqXskr9fC1eLKq
Extend the SubconsciousEngine tick with an offline 'orchestration_review' stage
that consumes stage-5 compressed history + the cumulative world-diff timeline and
emits short, dense steering directives injected into the reasoning core's prompt
on later cycles. The subconscious stays fully offline — the synthesis is a
tool-free provider chat on the hint:subconscious route under SubconsciousTainted
origin; no channels, no external effects.
- orchestration/steering.rs: SteeringDirective + ParsedSteering, the 20:1-aware
synthesis prompt (2-3 few-shot examples, STEERING_DIRECTIVE + expires_after_cycles
contract), and the parser (NONE vs contract-violation distinction, length cap).
- orchestration/store.rs: append-only steering_directives table (supersede chain +
cycle-count expiry), global reasoning-cycle counter, review cursor, and
unreviewed-compressed / recent-world-mutation readers.
- ops.rs::run_orchestration_review: load unreviewed data -> synthesize (retry once
on contract violation, skip on 2nd) -> persist directive (supersede prior) +
advance cursor + publish OrchestrationMessage{Subconscious} to the pinned local
window. Idempotent (keys on new compressed rows) and cheap when idle.
- ops.rs::seed_state: out-of-band writer pattern — bump the cycle counter and load
the current non-expired directive into state.subconscious_steering at cycle start;
the reasoning execute node (stage 5) weaves it into its prompt.
- engine.rs: run the review stage before memory_diff, independent of source syncs;
existing memory-diff / decide / notify_user behavior untouched.
Tests: steering parse/contract, supersede+expiry, cursor idempotence, offline
integration (seed rows -> tick -> directive persisted -> next cycle seeds it into
state + surfaces in the Subconscious window), and the isolation invariant (no
channel/outbound tools). 39 orchestration + 71 subconscious tests green.
Claude-Session: https://claude.ai/code/session_01MjTiUcPjbqXskr9fC1eLKq
…rewire
Expose the orchestration layer over JSON-RPC and rewire the Brain tab onto real
store classification + live socket updates + a Master composer.
Backend (Rust):
- orchestration/schemas.rs: internal-registry controllers (renderer-only)
openhuman.orchestration_{sessions_list, messages_list, send_master_message,
mark_read, status}; camelCase DTOs, computed active + unread, pinned master +
subconscious windows always present.
- orchestration/store.rs: list_sessions, list_messages_by_session (paged, keyed by
session_id so pinned windows aggregate across peers), unread_count / mark_chat_read
(read cursor), latest_master_peer (default send recipient).
- orchestration/bus.rs + core/socketio.rs: a broadcast bus fanned out as an
orchestration:message socket event ({agentId, sessionId, chatKind}) for live UI;
emitted for every persisted chat message (inbound, master, subconscious).
- core/all.rs: register the internal controllers + namespace description.
Frontend (delegated, integrated):
- app/src/lib/orchestration/orchestrationClient.ts: typed callCoreRpc wrappers +
PaymentRequiredError passthrough.
- app/src/lib/orchestration/useOrchestrationChats.ts: sessions/messages loads,
mark_read on open, status/steering, optimistic master send with rollback, and the
orchestration:message socket subscription doing targeted refetch.
- TinyPlaceOrchestrationTab.tsx: presentational over the hook; dropped
buildChats/chatKindForEnvelope heuristics; preserved pairing UX + testids; added
Master composer, steering chip, per-pane loading, unread badges.
- i18n keys across en + 13 locales.
Tests: 45 orchestration backend tests (schemas shape, chat resolution, read-surface
round-trip, unread/mark_read, latest_master_peer) + the tab test suite (8/8:
sessions/messages render, mark_read on open, master send optimistic append, socket
refetch, pairing). typecheck/lint/i18n:check green. Full-stack json_rpc_e2e deferred
to stage 8.
Claude-Session: https://claude.ai/code/session_01MjTiUcPjbqXskr9fC1eLKq
…tests Harden the orchestration layer to run unattended, audit logging, and prove the failure modes with automated tests. Hardening (ops.rs): - Every wake cycle awaits scheduler_gate::wait_for_capacity() — a Paused/Throttled gate defers the cycle (message stays in the store, cursor untouched), nothing dropped. - invoke_with_runtime: the idempotence cursor advances ONLY on a completed, DM-sent cycle, so a provider error mid-graph leaves no outbound DM and the next trigger resumes with no duplicate (dm_sent latch + deterministic cycle_id keep store writes idempotent). - record_last_error surfaces failures for orchestration.status (never a body). Observability: - orchestration.status now exposes ingest_cursor_lag (pending wake work) + last_error alongside steering directive + last tick (store::ingest_cursor_lag). - Nodes already log entry/exit with session_id/cycle_id/tick_id correlation ids. Tests: - provider_error_mid_graph_sends_no_dm_and_a_later_cycle_does_not_double_send - malformed_envelope_flood_all_fall_back_to_master_without_panic - orchestration_logs_never_reference_message_bodies (source-scan leak guard: no plaintext/body/seed in any log line) - read-surface round-trip + latest_master_peer (stage 7 data logic) 48 orchestration + 28 about_app tests green. Docs: - gitbooks/developing/architecture/orchestration.md (full narrative: wake graph, steering loop, RPC/UI, run-unattended guarantees). - about_app: Session Orchestration capability (Intelligence, Beta). Deferred to CI infra follow-up: the full tests/json_rpc_e2e.rs harness wiring + mock tiny.place relay, the WDIO Linux lane, and the coverage-gate lane. The loop is covered hermetically at the domain level (full-cycle e2e, ingest->graph->store, review->steering->next-cycle) plus the failure-mode suite above. Claude-Session: https://claude.ai/code/session_01MjTiUcPjbqXskr9fC1eLKq
📝 WalkthroughWalkthroughThis PR introduces a subconscious orchestration layer: a checkpointed wake graph coordinating front-end triage and reasoning agents, persisted compressed history/world-diff/steering directives, a debounced wake/review runtime sending signal DMs, a new JSON-RPC read surface with socket broadcast, and a reworked renderer orchestration tab with client, hook, and translations. ChangesSubconscious Orchestration Feature
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Ingest as OrchestrationIngest
participant Wake as OrchestrationWakeSubscriber
participant Ops as schedule_wake/invoke_with_runtime
participant Graph as run_orchestration_graph
participant Store as OrchestrationStore
participant Signal as tinyplace signal DM
Ingest->>Wake: DomainEvent::OrchestrationSessionMessage
Wake->>Ops: schedule_wake(agent_id, session_id)
Ops->>Store: seed_state / cursor idempotence check
Ops->>Graph: run_orchestration_graph(state)
Graph->>Store: persist compressed_history, world_diff
Graph->>Signal: send_dm(counterpart, body)
Graph-->>Ops: dm_sent = true
Ops->>Store: advance_cursor
sequenceDiagram
participant Tab as TinyPlaceOrchestrationTab
participant Hook as useOrchestrationChats
participant Client as orchestrationClient
participant Core as core RPC
Tab->>Hook: submitComposer(body)
Hook->>Hook: optimistic append to master chat
Hook->>Client: sendMasterMessage(body)
Client->>Core: openhuman.orchestration_send_master_message
Core-->>Hook: response / PaymentRequiredError
Hook->>Client: refetch messages/sessions
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e7bf83e386
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .with_update(OrchestrationUpdate::Pass1 { instructions }) | ||
| .with_goto(["execute"]), |
There was a problem hiding this comment.
Preserve pass-1 replies instead of always executing
When the front-end pass can answer immediately and follows the prompt by calling reply_to_channel, this branch still stores the returned text as Pass1 instructions and unconditionally routes to execute. That makes the quick-reply path unreachable: the reasoning agent receives the intended outbound reply as macro-instructions, can rewrite it, and does unnecessary LLM/tool work before any DM is sent. The front-end result needs to carry which decision tool fired and route reply_to_channel directly to send_dm.
Useful? React with 👍 / 👎.
| let latest = latest_seq(&state); | ||
| if !has_new_work(config, agent_id, session_id, latest) { |
There was a problem hiding this comment.
Track Master wake cursors with a monotonic value
For Master messages, classify_message persists every plain DM with seq = 0, but this new idempotence guard uses only latest_seq. After the first successful Master wake advances the cursor to 0, every later Master DM from the same peer also has latest = 0, so this check skips the wake graph and no reply/steering cycle runs. Since schedule_wake only excludes Subconscious messages, Master DMs are intended wake inputs; use a timestamp/relay id or assign a monotonic Master seq before applying this cursor.
Useful? React with 👍 / 👎.
| let latest_seq = messages.iter().map(|m| m.seq).max().unwrap_or(0); | ||
| let cycle_id = format!("{session_id}#{latest_seq}"); |
There was a problem hiding this comment.
Include agent id in cycle and checkpoint keys
Sessions are stored by (agent_id, session_id), and every peer's Master chat uses the same session_id (master), but this cycle id is derived only from session_id and latest_seq. The compressed-history/world-diff tables key by cycle_id, and the graph checkpoints under orchestration:<session_id>, so two linked agents with the same harness session id—or any two Master peers—can reuse/dedupe each other's rows and resume the wrong checkpointed state. Include the counterpart/agent id in the cycle/thread keys.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (5)
src/openhuman/agent_registry/agents/loader.rs (1)
1744-1753: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winMissing tier assertion for
reasoning_agent.
reasoning_agentis excluded fromother_builtins_default_to_worker_tier, but no test elsewhere pins its actualagent_tier. Unlikefrontend_agent(assertedAgentTier::Chatat Line 911), ifreasoning_agent's TOML omitsagent_tierit silently falls back to the struct default (Worker) with nothing catching the regression.Consider adding a
reasoning_agent_is_registered_on_reasoning_tier...test symmetric to thefrontend_agentone to lock its tier and tool surface.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/agent_registry/agents/loader.rs` around lines 1744 - 1753, Add a dedicated tier test for reasoning_agent, since other_builtins_default_to_worker_tier skips it but nothing currently asserts its actual tier. Mirror the existing frontend_agent registration test in loader.rs by locating the reasoning_agent registration path and asserting the expected AgentTier (so a missing agent_tier in TOML can’t silently fall back to Worker), and include any tool-surface checks that should stay tied to reasoning_agent.src/openhuman/orchestration/store.rs (1)
199-249: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
list_recent_messagesre-implementsmap_message_rowinstead of reusing it.
map_message_row(Lines 237-249) was specifically extracted to be reused acrossquery_maparms, butlist_recent_messages(Lines 353-381) duplicates the identical row-mapping closure inline instead of calling it. Any future schema/column change now has to be kept in sync in two places.♻️ Proposed fix
let rows = stmt - .query_map(params![agent_id, session_id, limit], |row| { - let chat_kind: String = row.get(3)?; - Ok(OrchestrationMessage { - id: row.get(0)?, - agent_id: row.get(1)?, - session_id: row.get(2)?, - chat_kind: crate::openhuman::orchestration::types::ChatKind::from_str(&chat_kind), - role: row.get(4)?, - body: row.get(5)?, - timestamp: row.get(6)?, - seq: row.get(7)?, - }) - })? + .query_map(params![agent_id, session_id, limit], map_message_row)? .collect::<std::result::Result<Vec<_>, _>>()?;Also applies to: 353-381
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/orchestration/store.rs` around lines 199 - 249, `list_recent_messages` is duplicating the same row-to-message mapping logic already centralized in `map_message_row`, which creates a second place to maintain the schema mapping. Update `list_recent_messages` to reuse `map_message_row` for its `query_map` call instead of an inline closure, keeping the mapping logic in one place and aligned with `list_messages_by_session`.src/openhuman/orchestration/types.rs (1)
131-139: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider implementing
std::str::FromStrinstead of an inherentfrom_str.Naming an inherent method
from_strshadows the standard trait method; clippy'sshould_implement_traitlint commonly flags this pattern. Since this variant is infallible, either rename it (e.g.,parse_kind) or implementFromStrwithtype Err = Infallibleto satisfy the trait shape and enable.parse().♻️ Option: implement `FromStr`
-impl ChatKind { - pub fn as_str(&self) -> &'static str { +impl std::str::FromStr for ChatKind { + type Err = std::convert::Infallible; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok(match s { + "session" => ChatKind::Session, + "subconscious" => ChatKind::Subconscious, + _ => ChatKind::Master, + }) + } +} + +impl ChatKind { + pub fn as_str(&self) -> &'static str { match self { ChatKind::Master => "master", ChatKind::Subconscious => "subconscious", ChatKind::Session => "session", } } - - /// Parse the persisted string form back into a [`ChatKind`]. Unknown values - /// fall back to [`ChatKind::Master`] (the safe, non-session default). - pub fn from_str(s: &str) -> Self { - match s { - "session" => ChatKind::Session, - "subconscious" => ChatKind::Subconscious, - _ => ChatKind::Master, - } - } }Since this is speculative on clippy lint configuration in CI, please confirm whether clippy is run with
should_implement_traitenabled (default in clippy) before deciding priority.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/orchestration/types.rs` around lines 131 - 139, The inherent ChatKind::from_str method is shadowing the standard parsing trait and may trigger clippy’s should_implement_trait lint. Update the parsing API in src/openhuman/orchestration/types.rs by either renaming the method to something non-conflicting like parse_kind, or implementing std::str::FromStr for ChatKind with an infallible error type so callers can use the standard .parse() path. Check whether clippy is enabled with should_implement_trait in CI before choosing the preferred fix.app/src/lib/orchestration/orchestrationClient.ts (1)
77-81: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMinor type inconsistency:
chatKindtyped asstringinstead ofOrchestrationChatKind.
SessionSummary.chatKindandOrchestrationMessage.chatKinduse theOrchestrationChatKindunion, butOrchestrationMessageEvent.chatKind(the socket payload) is a plainstring. Since these appear to represent the same domain concept, tightening the type would give consumers safer narrowing.♻️ Suggested type tightening
export interface OrchestrationMessageEvent { agentId: string; sessionId: string; - chatKind: string; + chatKind: OrchestrationChatKind; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/lib/orchestration/orchestrationClient.ts` around lines 77 - 81, The `OrchestrationMessageEvent` payload currently uses a generic `string` for `chatKind`, which is inconsistent with `SessionSummary.chatKind` and `OrchestrationMessage.chatKind`. Update the `OrchestrationMessageEvent` interface in `orchestrationClient.ts` to use the existing `OrchestrationChatKind` union so the socket payload matches the same domain type. Make sure any references that consume `OrchestrationMessageEvent` still compile with the tighter `chatKind` type.app/src/components/intelligence/TinyPlaceOrchestrationTab.test.tsx (1)
261-279: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSocket mock leaks across tests.
getSocketMock.mockReturnValue(socket as never)(line 269) overrides the shared mock's return value with a test-local socket object.vi.clearAllMocks()inbeforeEachonly clears call history, notmockReturnValue/mockImplementation, so every test that runs after this one in the same file will keep receiving this localsocketinstead of the shared module-level socket declared in thesocketServicemock (lines 36-39). It's currently harmless because no later test asserts on socket calls, but it's a latent test-isolation hazard for future additions.🔧 Suggested fix
it('refetches when an orchestration:message socket event fires', async () => { let handler: ((payload: unknown) => void) | undefined; const socket = { on: vi.fn((event: string, cb: (payload: unknown) => void) => { if (event === 'orchestration:message') handler = cb; }), off: vi.fn(), }; getSocketMock.mockReturnValue(socket as never); render(<TinyPlaceOrchestrationTab />); await waitFor(() => expect(sessionsListMock).toHaveBeenCalled()); const initialCalls = sessionsListMock.mock.calls.length; expect(handler).toBeDefined(); handler?.({ agentId: '`@worker-alpha`', sessionId: 'master', chatKind: 'master' }); await waitFor(() => expect(sessionsListMock.mock.calls.length).toBeGreaterThan(initialCalls)); + + // Restore the default shared socket so later tests aren't affected. + getSocketMock.mockReturnValue(defaultSocket as never); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/intelligence/TinyPlaceOrchestrationTab.test.tsx` around lines 261 - 279, The test is overriding the shared getSocketMock return value with a local socket, which can leak into later tests because vi.clearAllMocks() does not reset mock implementations. Update the orchestration message test in TinyPlaceOrchestrationTab.test.tsx to avoid permanently changing getSocketMock, and instead use the shared socket mock from the socketService setup or restore/reset the mock implementation after the test. Keep the event handler assertion and refetch trigger logic the same, but ensure the mock state is isolated per test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/lib/orchestration/useOrchestrationChats.ts`:
- Around line 177-200: The shared messagesState updates in loadMessages are not
scoped to the active chat, so stale responses can overwrite the UI for a
different selection. Update loadMessages in useOrchestrationChats to capture the
chatKey being loaded and only apply setMessagesByChat/setMessagesState when that
chat is still the currently selected one (and mountedRef.current is true). Apply
the same guard to the loading/ok/error branches referenced by the related state
writes so out-of-order responses from an old chat cannot affect the active
chat’s messagesState.
- Line 280: The fire-and-forget `loadSessions()` calls in
`useOrchestrationChats` can reject without being handled, which risks unhandled
promise rejections. Update both call sites (the `sendMaster` success path and
the socket message handler) to either wrap `loadSessions` in local try/catch or
append a `.catch` that swallows/logs the error similarly to `refreshStatus`, so
`orchestrationClient.sessionsList()` failures remain best-effort and do not
bubble globally.
- Around line 248-257: `selectChat` in `useOrchestrationChats` currently returns
early when the requested chat matches `selectedIdRef.current`, which prevents
retrying a failed message load for the active chat. Add a dedicated reload path
such as `retryMessages` to `UseOrchestrationChatsResult` that always re-invokes
`loadMessages` and `markRead` for the current `selected.id`, and update
`TinyPlaceOrchestrationTab`’s messages-error retry button to call that new
method instead of `selectChat(selected.id)`.
- Around line 313-332: The orchestration message listeners are attached only
when getSocket() is already available, so this effect can miss the first
connection from socketService.connect(...). Update the useEffect in
useOrchestrationChats to subscribe through the socket service’s queued listener
mechanism or make it rerun when the socket instance becomes available, and keep
the existing handler logic for orchestration:message/orchestration_message
intact.
In `@gitbooks/developing/architecture/orchestration.md`:
- Around line 16-31: The architecture diagram is an unlabeled fenced code block,
which triggers the markdownlint fenced-code-language warning. Update the
markdown fence in the orchestration documentation to include an appropriate
language tag such as text so the block passes lint. Keep the diagram content
unchanged and ensure the fix is applied to the specific fenced block showing the
Claude Code / Codex session flow.
In `@src/openhuman/config/schema/orchestration.rs`:
- Around line 65-69: The orchestration config field subagent_concurrency is
defined in the schema but never used, so the reasoning execute node ignores the
documented concurrency cap. Thread this value from the config struct into the
reasoning execute path and use it where sub-agents are spawned or scheduled.
Update the execute node logic to read subagent_concurrency from the
orchestration config instead of relying on a hardcoded limit, keeping
default_subagent_concurrency as the fallback source.
In `@src/openhuman/orchestration/schemas.rs`:
- Around line 67-76: The messages_list schema is missing the optional limit
input even though handle_messages_list supports it. Update the ControllerSchema
entry for messages_list to include an optional u64 limit alongside chat and
before, matching the handler’s default/capped pagination behavior. Use the
existing required_str and optional_str pattern in orchestration_messages_list so
the schema reflects the actual function signature.
In `@src/openhuman/orchestration/store.rs`:
- Around line 527-544: `list_unreviewed_compressed` currently pages only by
`created_at`, so rows sharing the same timestamp can be skipped when the review
cursor advances. Update the compressed-history scan to use a composite cursor
with `created_at` plus a unique tie-breaker such as `cycle_id`, and make the
query order by both fields consistently. Also adjust the cursor advancement
logic around `set_review_cursor` so it records both parts of the composite
position and resumes safely without missing same-timestamp rows.
- Around line 199-233: The paging logic in list_messages_by_session currently
uses only a timestamp cursor even though the results are ordered by timestamp
and seq, which can skip messages that share the same timestamp. Update the
cursor contract in the RPC schema and caller to pass both timestamp and seq, and
change the query/filter in list_messages_by_session to compare the composite
(timestamp, seq) boundary consistently with the existing ORDER BY timestamp
DESC, seq DESC.
---
Nitpick comments:
In `@app/src/components/intelligence/TinyPlaceOrchestrationTab.test.tsx`:
- Around line 261-279: The test is overriding the shared getSocketMock return
value with a local socket, which can leak into later tests because
vi.clearAllMocks() does not reset mock implementations. Update the orchestration
message test in TinyPlaceOrchestrationTab.test.tsx to avoid permanently changing
getSocketMock, and instead use the shared socket mock from the socketService
setup or restore/reset the mock implementation after the test. Keep the event
handler assertion and refetch trigger logic the same, but ensure the mock state
is isolated per test.
In `@app/src/lib/orchestration/orchestrationClient.ts`:
- Around line 77-81: The `OrchestrationMessageEvent` payload currently uses a
generic `string` for `chatKind`, which is inconsistent with
`SessionSummary.chatKind` and `OrchestrationMessage.chatKind`. Update the
`OrchestrationMessageEvent` interface in `orchestrationClient.ts` to use the
existing `OrchestrationChatKind` union so the socket payload matches the same
domain type. Make sure any references that consume `OrchestrationMessageEvent`
still compile with the tighter `chatKind` type.
In `@src/openhuman/agent_registry/agents/loader.rs`:
- Around line 1744-1753: Add a dedicated tier test for reasoning_agent, since
other_builtins_default_to_worker_tier skips it but nothing currently asserts its
actual tier. Mirror the existing frontend_agent registration test in loader.rs
by locating the reasoning_agent registration path and asserting the expected
AgentTier (so a missing agent_tier in TOML can’t silently fall back to Worker),
and include any tool-surface checks that should stay tied to reasoning_agent.
In `@src/openhuman/orchestration/store.rs`:
- Around line 199-249: `list_recent_messages` is duplicating the same
row-to-message mapping logic already centralized in `map_message_row`, which
creates a second place to maintain the schema mapping. Update
`list_recent_messages` to reuse `map_message_row` for its `query_map` call
instead of an inline closure, keeping the mapping logic in one place and aligned
with `list_messages_by_session`.
In `@src/openhuman/orchestration/types.rs`:
- Around line 131-139: The inherent ChatKind::from_str method is shadowing the
standard parsing trait and may trigger clippy’s should_implement_trait lint.
Update the parsing API in src/openhuman/orchestration/types.rs by either
renaming the method to something non-conflicting like parse_kind, or
implementing std::str::FromStr for ChatKind with an infallible error type so
callers can use the standard .parse() path. Check whether clippy is enabled with
should_implement_trait in CI before choosing the preferred fix.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: df562305-69be-4081-801b-a290c83310a9
📒 Files selected for processing (53)
app/src/components/intelligence/TinyPlaceOrchestrationTab.test.tsxapp/src/components/intelligence/TinyPlaceOrchestrationTab.tsxapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.tsapp/src/lib/orchestration/orchestrationClient.tsapp/src/lib/orchestration/useOrchestrationChats.tsgitbooks/developing/architecture/orchestration.mdsrc/core/all.rssrc/core/jsonrpc.rssrc/core/socketio.rssrc/openhuman/about_app/catalog_data.rssrc/openhuman/agent_registry/agents/loader.rssrc/openhuman/config/schema/orchestration.rssrc/openhuman/orchestration/bus.rssrc/openhuman/orchestration/frontend_agent/agent.tomlsrc/openhuman/orchestration/frontend_agent/graph.rssrc/openhuman/orchestration/frontend_agent/mod.rssrc/openhuman/orchestration/frontend_agent/prompt.mdsrc/openhuman/orchestration/frontend_agent/prompt.rssrc/openhuman/orchestration/graph/compress.rssrc/openhuman/orchestration/graph/mod.rssrc/openhuman/orchestration/graph/state.rssrc/openhuman/orchestration/graph/tests.rssrc/openhuman/orchestration/graph/world_diff.rssrc/openhuman/orchestration/mod.rssrc/openhuman/orchestration/ops.rssrc/openhuman/orchestration/reasoning_agent/agent.tomlsrc/openhuman/orchestration/reasoning_agent/graph.rssrc/openhuman/orchestration/reasoning_agent/mod.rssrc/openhuman/orchestration/reasoning_agent/prompt.mdsrc/openhuman/orchestration/reasoning_agent/prompt.rssrc/openhuman/orchestration/schemas.rssrc/openhuman/orchestration/steering.rssrc/openhuman/orchestration/store.rssrc/openhuman/orchestration/tools.rssrc/openhuman/orchestration/types.rssrc/openhuman/subconscious/engine.rssrc/openhuman/tinyagents/topology.rssrc/openhuman/tinyplace/mod.rssrc/openhuman/tools/mod.rssrc/openhuman/tools/ops.rs
| const loadMessages = useCallback(async (chatKey: string) => { | ||
| setMessagesState({ status: 'loading' }); | ||
| try { | ||
| const result = await orchestrationClient.messagesList({ | ||
| chat: chatKey, | ||
| limit: MESSAGE_LIMIT, | ||
| }); | ||
| if (!mountedRef.current) return; | ||
| setMessagesByChat(prev => ({ | ||
| ...prev, | ||
| [chatKey]: sortMessages(result.messages.map(mapMessage)), | ||
| })); | ||
| setMessagesState({ status: 'ok' }); | ||
| } catch (error) { | ||
| if (!mountedRef.current) return; | ||
| if (error instanceof PaymentRequiredError) { | ||
| setSessionsState({ status: 'payment_required' }); | ||
| setMessagesState({ status: 'idle' }); | ||
| return; | ||
| } | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| setMessagesState({ status: 'error', message }); | ||
| } | ||
| }, []); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Race condition: shared messagesState isn't scoped per chat.
loadMessages unconditionally writes messagesState (loading/ok/error) regardless of whether chatKey is still the selected chat when the response arrives. If a user switches chats while a request is in flight, an out-of-order response for the previously selected chat can overwrite the currently displayed chat's state — e.g. a late failure for chat A can stamp an error banner over chat B's successfully-loaded messages, or a late success for A can silently clear a real error for B (this also masks the retry button described below, since the wrong branch renders).
🔒️ Suggested fix: guard state writes by the still-selected chat
const loadMessages = useCallback(async (chatKey: string) => {
- setMessagesState({ status: 'loading' });
+ if (chatKey === selectedIdRef.current) setMessagesState({ status: 'loading' });
try {
const result = await orchestrationClient.messagesList({
chat: chatKey,
limit: MESSAGE_LIMIT,
});
if (!mountedRef.current) return;
setMessagesByChat(prev => ({
...prev,
[chatKey]: sortMessages(result.messages.map(mapMessage)),
}));
- setMessagesState({ status: 'ok' });
+ if (chatKey === selectedIdRef.current) setMessagesState({ status: 'ok' });
} catch (error) {
if (!mountedRef.current) return;
if (error instanceof PaymentRequiredError) {
setSessionsState({ status: 'payment_required' });
- setMessagesState({ status: 'idle' });
+ if (chatKey === selectedIdRef.current) setMessagesState({ status: 'idle' });
return;
}
- const message = error instanceof Error ? error.message : String(error);
- setMessagesState({ status: 'error', message });
+ if (chatKey === selectedIdRef.current) {
+ const message = error instanceof Error ? error.message : String(error);
+ setMessagesState({ status: 'error', message });
+ }
}
}, []);Also applies to: 211-215, 230-246
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/lib/orchestration/useOrchestrationChats.ts` around lines 177 - 200,
The shared messagesState updates in loadMessages are not scoped to the active
chat, so stale responses can overwrite the UI for a different selection. Update
loadMessages in useOrchestrationChats to capture the chatKey being loaded and
only apply setMessagesByChat/setMessagesState when that chat is still the
currently selected one (and mountedRef.current is true). Apply the same guard to
the loading/ok/error branches referenced by the related state writes so
out-of-order responses from an old chat cannot affect the active chat’s
messagesState.
| const selectChat = useCallback( | ||
| (chatKey: string) => { | ||
| if (chatKey === selectedIdRef.current) return; | ||
| setSelectedId(chatKey); | ||
| setMasterError(null); | ||
| void loadMessages(chatKey); | ||
| void markRead(chatKey); | ||
| }, | ||
| [loadMessages, markRead] | ||
| ); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
selectChat no-ops on the current chat, breaking the message-error retry button.
selectChat early-returns when chatKey === selectedIdRef.current. TinyPlaceOrchestrationTab's messages-error retry button calls selectChat(selected.id) — which is always the currently selected chat id — so the retry action is a permanent no-op: loadMessages/markRead never re-fire and the user has no way to recover from a message-load failure without navigating away and back.
🐛 Proposed fix: expose a dedicated reload path
const selectChat = useCallback(
(chatKey: string) => {
if (chatKey === selectedIdRef.current) return;
setSelectedId(chatKey);
setMasterError(null);
void loadMessages(chatKey);
void markRead(chatKey);
},
[loadMessages, markRead]
);
+
+ const retryMessages = useCallback(() => {
+ void loadMessages(selectedIdRef.current);
+ }, [loadMessages]);Then expose retryMessages in UseOrchestrationChatsResult and have the Tab's messages-error retry button call it instead of selectChat(selected.id).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/lib/orchestration/useOrchestrationChats.ts` around lines 248 - 257,
`selectChat` in `useOrchestrationChats` currently returns early when the
requested chat matches `selectedIdRef.current`, which prevents retrying a failed
message load for the active chat. Add a dedicated reload path such as
`retryMessages` to `UseOrchestrationChatsResult` that always re-invokes
`loadMessages` and `markRead` for the current `selected.id`, and update
`TinyPlaceOrchestrationTab`’s messages-error retry button to call that new
method instead of `selectChat(selected.id)`.
| if (!mountedRef.current) return true; | ||
| // Reconcile against the authoritative server state. | ||
| void loadMessages(MASTER_CHAT_KEY); | ||
| void loadSessions(); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Unhandled promise rejection risk: void loadSessions() has no .catch.
Unlike refreshStatus (which swallows its own errors), loadSessions (lines 211-215) has no internal try/catch. It's called fire-and-forget via void loadSessions(); both in sendMaster's success branch (line 280) and in the socket message handler (line 320). If orchestrationClient.sessionsList() rejects at either call site, it becomes an unhandled promise rejection (console noise / potential global error-handler / Sentry noise) instead of being surfaced gracefully like the rest of the file's best-effort calls.
🔧 Suggested fix
- void loadMessages(MASTER_CHAT_KEY);
- void loadSessions();
+ void loadMessages(MASTER_CHAT_KEY);
+ void loadSessions().catch(() => {});(and similarly at the socket-handler call site)
Also applies to: 320-320
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/lib/orchestration/useOrchestrationChats.ts` at line 280, The
fire-and-forget `loadSessions()` calls in `useOrchestrationChats` can reject
without being handled, which risks unhandled promise rejections. Update both
call sites (the `sendMaster` success path and the socket message handler) to
either wrap `loadSessions` in local try/catch or append a `.catch` that
swallows/logs the error similarly to `refreshStatus`, so
`orchestrationClient.sessionsList()` failures remain best-effort and do not
bubble globally.
| useEffect(() => { | ||
| const socket = socketService.getSocket(); | ||
| if (!socket) return; | ||
| const handler = (payload: unknown) => { | ||
| const event = payload as OrchestrationMessageEvent | null; | ||
| if (!event || typeof event !== 'object') return; | ||
| const affected = event.chatKind === 'session' ? event.sessionId : event.chatKind; | ||
| void loadSessions(); | ||
| void refreshStatus(); | ||
| if (affected && affected === selectedIdRef.current) { | ||
| void loadMessages(affected); | ||
| } | ||
| }; | ||
| socket.on('orchestration:message', handler); | ||
| socket.on('orchestration_message', handler); | ||
| return () => { | ||
| socket.off('orchestration:message', handler); | ||
| socket.off('orchestration_message', handler); | ||
| }; | ||
| }, [loadSessions, loadMessages, refreshStatus]); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n "getSocket" -A15 app/src/services/socketService.tsRepository: tinyhumansai/openhuman
Length of output: 5290
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== socketService connect/getSocket usage =="
rg -n "socketService\.(connect|getSocket|disconnect|onSocket|offSocket)|getSocket\(\)" app/src -g '!**/*.test.*'
echo
echo "== socketService.ts outline =="
ast-grep outline app/src/services/socketService.ts --view expanded
echo
echo "== useOrchestrationChats.ts outline =="
ast-grep outline app/src/lib/orchestration/useOrchestrationChats.ts --view expandedRepository: tinyhumansai/openhuman
Length of output: 4948
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '150,260p' app/src/services/socketService.ts
echo
sed -n '260,360p' app/src/services/socketService.ts
echo
sed -n '1,220p' app/src/lib/orchestration/useOrchestrationChats.tsRepository: tinyhumansai/openhuman
Length of output: 16370
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== socketService.ts connect logic =="
sed -n '150,240p' app/src/services/socketService.ts
echo
echo "== socketService.ts connection listeners =="
sed -n '240,360p' app/src/services/socketService.ts
echo
echo "== socketService.ts pending listener mechanics =="
sed -n '360,520p' app/src/services/socketService.ts
echo
echo "== useOrchestrationChats.ts relevant effect and socket access =="
rg -n "socketService|getSocket|orchestration:message|orchestration_message|useEffect" app/src/lib/orchestration/useOrchestrationChats.ts -n -A25 -B10Repository: tinyhumansai/openhuman
Length of output: 20843
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== socketService.connect call sites =="
rg -n "socketService\.connect\(|\.connect\(" app/src -g '!**/*.test.*' | sed -n '1,200p'
echo
echo "== app bootstrap / providers =="
rg -n "socketService|SocketService|useOrchestrationChats|OrchestrationChats|connect\(" app/src/main.* app/src/App.* app/src/**/providers* app/src/**/Provider* app/src/**/index.* -g '!**/*.test.*' | sed -n '1,240p'Repository: tinyhumansai/openhuman
Length of output: 1818
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== useOrchestrationChats effect vicinity =="
sed -n '280,360p' app/src/lib/orchestration/useOrchestrationChats.ts
echo
echo "== socketService on/addListener behavior =="
rg -n "pendingListeners|listenerMap|on\(|off\(|emitPendingListeners|queue" app/src/services/socketService.ts -A20 -B20
echo
echo "== any explicit readiness signal or subscribe-to-connect API =="
rg -n "backend.*connected|socket.*connected|setBackend\\(|isConnected\\(|getSocket\\(|socketReady|ready.*socket" app/src -g '!**/*.test.*' | sed -n '1,240p'Repository: tinyhumansai/openhuman
Length of output: 32412
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== SocketProvider.tsx =="
sed -n '1,220p' app/src/providers/SocketProvider.tsx
echo
echo "== useOrchestrationChats consumers =="
rg -n "useOrchestrationChats\\(" app/src -g '!**/*.test.*'
echo
echo "== orchestration-related provider/tree references =="
rg -n "Orchestration|TinyPlace|Brain tab|orchestration chats" app/src -g '!**/*.test.*'Repository: tinyhumansai/openhuman
Length of output: 50378
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '150,220p' app/src/components/intelligence/TinyPlaceOrchestrationTab.tsx
echo
sed -n '300,360p' app/src/pages/Brain.tsxRepository: tinyhumansai/openhuman
Length of output: 4909
Subscribe after socket creation. socketService.connect(...) is async and getSocket() can be null when this effect runs, so the orchestration:message handlers never attach for that render. Use the service’s queued listener path or rerun when the socket instance becomes available.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/lib/orchestration/useOrchestrationChats.ts` around lines 313 - 332,
The orchestration message listeners are attached only when getSocket() is
already available, so this effect can miss the first connection from
socketService.connect(...). Update the useEffect in useOrchestrationChats to
subscribe through the socket service’s queued listener mechanism or make it
rerun when the socket instance becomes available, and keep the existing handler
logic for orchestration:message/orchestration_message intact.
| ``` | ||
| Claude Code / Codex session | ||
| └─ tinyplace harness wrapper — tails the session JSONL → SessionEnvelopeV1 | ||
| └─ Signal E2E DM → owner agent's tiny.place inbox [tagged sessionId, source, role] | ||
| └─ ingest (decrypt-once → classify → persist → ack) orchestration/ingest.rs | ||
| └─ OrchestrationSessionMessage event → debounced wake | ||
| └─ THE WAKE GRAPH (one tinyagents CompiledGraph) orchestration/graph/ | ||
| normalize → frontend(1) → execute → compress → world_diff | ||
| ▲ │ | ||
| └───────────────────────────────────┘ | ||
| │ | ||
| └─(channel_response)─► send_dm ─► context_guard ─► done | ||
| └─ subconscious tick (offline, cron/heartbeat) — reviews compressed history + | ||
| cumulative world diff → emits a steering directive that later cycles inject | ||
| UI: Brain → Orchestration tab (orchestration.* RPC + orchestration:message socket) | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a fence language here.
This code block triggers the markdownlint fenced-code-language warning. Mark it as text (or another appropriate language) so the docs pass lint.
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 16-16: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@gitbooks/developing/architecture/orchestration.md` around lines 16 - 31, The
architecture diagram is an unlabeled fenced code block, which triggers the
markdownlint fenced-code-language warning. Update the markdown fence in the
orchestration documentation to include an appropriate language tag such as text
so the block passes lint. Keep the diagram content unchanged and ensure the fix
is applied to the specific fenced block showing the Claude Code / Codex session
flow.
Source: Linters/SAST tools
| /// Maximum concurrent execution sub-agents the reasoning `execute` node may | ||
| /// spawn per cycle. Default: `2`. | ||
| #[serde(default = "default_subagent_concurrency")] | ||
| pub subagent_concurrency: u32, | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n 'subagent_concurrency' --type=rustRepository: tinyhumansai/openhuman
Length of output: 160
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files 'src/openhuman/**' | sed -n '1,200p'
echo "== search for concurrency-related config uses =="
rg -n --hidden --glob '!target' --glob '!node_modules' \
'subagent_concurrency|default_subagent_concurrency|max_supersteps|context_evict_threshold|message_window|concurrent.*sub-agent|spawn.*sub-agent|reasoning_agent|execute node|execute\(' src/openhuman
echo "== search for execute/reasoning domain files =="
fd -a 'reasoning|execute|orchestration' src/openhumanRepository: tinyhumansai/openhuman
Length of output: 50379
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the orchestration config file and nearby domain files.
sed -n '1,220p' src/openhuman/config/schema/orchestration.rs
echo "== outline candidate files =="
for f in $(fd -a 'orchestration|reasoning|execute' src/openhuman); do
echo "--- $f ---"
wc -l "$f"
doneRepository: tinyhumansai/openhuman
Length of output: 3219
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== relevant file names =="
fd -a 'orchestration\.rs|reasoning|execute|state|graph' src/openhuman
echo "== references to orchestration config fields =="
rg -n --hidden --glob '!target' --glob '!node_modules' \
'message_window|max_supersteps|context_evict_threshold|subagent_concurrency|seed_state|run_orchestration_graph|default_subagent_concurrency' src/openhuman
echo "== inspect orchestration config file =="
sed -n '1,220p' src/openhuman/config/schema/orchestration.rsRepository: tinyhumansai/openhuman
Length of output: 12191
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== orchestration graph outline =="
ast-grep outline src/openhuman/orchestration/graph/mod.rs --view expanded | sed -n '1,220p'
echo "== reasoning agent graph outline =="
ast-grep outline src/openhuman/orchestration/reasoning_agent/graph.rs --view expanded | sed -n '1,220p'
echo "== targeted searches =="
rg -n --hidden --glob '!target' \
'subagent_concurrency|spawn|parallel|concurrent|execute node|reasoning_agent|ExecuteOutcome' \
src/openhuman/orchestration/graph/mod.rs \
src/openhuman/orchestration/reasoning_agent/graph.rsRepository: tinyhumansai/openhuman
Length of output: 2874
Wire subagent_concurrency into the reasoning execute path. The config field is currently unused, so the documented cap has no effect.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/openhuman/config/schema/orchestration.rs` around lines 65 - 69, The
orchestration config field subagent_concurrency is defined in the schema but
never used, so the reasoning execute node ignores the documented concurrency
cap. Thread this value from the config struct into the reasoning execute path
and use it where sub-agents are spawned or scheduled. Update the execute node
logic to read subagent_concurrency from the orchestration config instead of
relying on a hardcoded limit, keeping default_subagent_concurrency as the
fallback source.
| "orchestration_messages_list" => ControllerSchema { | ||
| namespace: "orchestration", | ||
| function: "messages_list", | ||
| description: "List messages for a chat: \"master\", \"subconscious\", or a harness session id.", | ||
| inputs: vec![ | ||
| required_str("chat", "Chat key: \"master\" | \"subconscious\" | <sessionId>."), | ||
| optional_str("before", "Exclusive ISO timestamp to page backwards from."), | ||
| ], | ||
| outputs: vec![json_output("result", "{ messages: OrchestrationMessage[] }.")], | ||
| }, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm the numeric TypeSchema variant available for an integer field.
rg -nP -C2 'enum TypeSchema' src/coreRepository: tinyhumansai/openhuman
Length of output: 411
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- TypeSchema ---'
sed -n '83,130p' src/core/mod.rs
echo '--- orchestration schemas around messages_list ---'
sed -n '60,90p' src/openhuman/orchestration/schemas.rs
echo '--- handle_messages_list ---'
sed -n '240,275p' src/openhuman/orchestration/schemas.rs
echo '--- optional_* helpers in orchestration schemas ---'
rg -n "fn optional_|fn required_|optional_int|optional_str|required_str|json_output" src/openhuman/orchestration/schemas.rs
echo '--- orchestration client messagesList ---'
rg -n "messagesList|limit" src -g '*orchestrationClient*' -g '*orchestration*'Repository: tinyhumansai/openhuman
Length of output: 6108
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- helper definitions ---'
sed -n '402,440p' src/openhuman/orchestration/schemas.rs
echo '--- search for orchestrationClient/messagesList in repo ---'
rg -n "orchestrationClient|messagesList|messages_list" src -g '*ts' -g '*tsx' -g '*js' -g '*jsx'Repository: tinyhumansai/openhuman
Length of output: 1277
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "fn optional_(u64|i64|int)|fn required_(u64|i64|int)|TypeSchema::(U64|I64)" src/openhuman src/coreRepository: tinyhumansai/openhuman
Length of output: 26221
Add limit to messages_list schema handle_messages_list already accepts an optional limit (default 100, capped at 500), so the schema should declare it too as an optional u64.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/openhuman/orchestration/schemas.rs` around lines 67 - 76, The
messages_list schema is missing the optional limit input even though
handle_messages_list supports it. Update the ControllerSchema entry for
messages_list to include an optional u64 limit alongside chat and before,
matching the handler’s default/capped pagination behavior. Use the existing
required_str and optional_str pattern in orchestration_messages_list so the
schema reflects the actual function signature.
| /// List messages for a chat keyed by `session_id` alone (so the pinned `master` / | ||
| /// `subconscious` windows aggregate across peers). Newest `limit` returned in | ||
| /// chronological order; `before` (exclusive timestamp) pages backwards. | ||
| pub fn list_messages_by_session( | ||
| conn: &Connection, | ||
| session_id: &str, | ||
| limit: u32, | ||
| before: Option<&str>, | ||
| ) -> Result<Vec<OrchestrationMessage>> { | ||
| let rows = match before { | ||
| Some(before) => { | ||
| let mut stmt = conn.prepare( | ||
| "SELECT id, agent_id, session_id, chat_kind, role, body, timestamp, seq | ||
| FROM messages WHERE session_id = ?1 AND timestamp < ?2 | ||
| ORDER BY timestamp DESC, seq DESC LIMIT ?3", | ||
| )?; | ||
| let rows = stmt | ||
| .query_map(params![session_id, before, limit], map_message_row)? | ||
| .collect::<std::result::Result<Vec<_>, _>>()?; | ||
| rows | ||
| } | ||
| None => { | ||
| let mut stmt = conn.prepare( | ||
| "SELECT id, agent_id, session_id, chat_kind, role, body, timestamp, seq | ||
| FROM messages WHERE session_id = ?1 | ||
| ORDER BY timestamp DESC, seq DESC LIMIT ?2", | ||
| )?; | ||
| let rows = stmt | ||
| .query_map(params![session_id, limit], map_message_row)? | ||
| .collect::<std::result::Result<Vec<_>, _>>()?; | ||
| rows | ||
| } | ||
| }; | ||
| Ok(rows.into_iter().rev().collect()) | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== file excerpt =="
sed -n '160,280p' src/openhuman/orchestration/store.rs
echo
echo "== search callers =="
rg -n "list_messages_by_session|before" src/openhuman -g '!**/target/**'
echo
echo "== related schema references =="
rg -n "session_id.*before|before.*session_id|timestamp.*seq|chat_kind" src/openhuman -g '!**/target/**'Repository: tinyhumansai/openhuman
Length of output: 50379
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== candidate files =="
git ls-files 'src/openhuman/**' | rg 'orchestration|message|schema|rpc'
echo
echo "== search message insert / timestamp generation =="
rg -n "timestamp|seq|insert.*messages|INSERT INTO messages|list_messages_by_session|before" src/openhuman -g '!**/target/**'Repository: tinyhumansai/openhuman
Length of output: 50379
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== orchestration store tests around list_messages_by_session =="
sed -n '760,845p' src/openhuman/orchestration/store.rs
echo
echo "== orchestration schema cursor description =="
sed -n '60,90p' src/openhuman/orchestration/schemas.rs
echo
echo "== message insert/update paths =="
rg -n "INSERT INTO messages|UPDATE messages|timestamp =|seq =" src/openhuman/orchestration/store.rsRepository: tinyhumansai/openhuman
Length of output: 5528
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== message table schema and insert path =="
sed -n '1,170p' src/openhuman/orchestration/store.rs
echo
echo "== surrounding insert_message implementation =="
sed -n '170,240p' src/openhuman/orchestration/store.rs
echo
echo "== test message helper =="
sed -n '845,930p' src/openhuman/orchestration/store.rsRepository: tinyhumansai/openhuman
Length of output: 10037
Use a composite (timestamp, seq) cursor for paging. The query already sorts by (timestamp DESC, seq DESC), but before filters only on timestamp, so messages that share the boundary timestamp can be skipped. Update the RPC schema/caller to pass both values.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/openhuman/orchestration/store.rs` around lines 199 - 233, The paging
logic in list_messages_by_session currently uses only a timestamp cursor even
though the results are ordered by timestamp and seq, which can skip messages
that share the same timestamp. Update the cursor contract in the RPC schema and
caller to pass both timestamp and seq, and change the query/filter in
list_messages_by_session to compare the composite (timestamp, seq) boundary
consistently with the existing ORDER BY timestamp DESC, seq DESC.
| /// Compressed-history rows not yet reviewed (created_at > cursor), oldest-first, | ||
| /// bounded. Returns `(created_at, text)`. | ||
| pub fn list_unreviewed_compressed( | ||
| conn: &Connection, | ||
| since_created_at: &str, | ||
| limit: u32, | ||
| ) -> Result<Vec<(String, String)>> { | ||
| let mut stmt = conn.prepare( | ||
| "SELECT created_at, text FROM compressed_history | ||
| WHERE created_at > ?1 ORDER BY created_at ASC LIMIT ?2", | ||
| )?; | ||
| let rows = stmt | ||
| .query_map(params![since_created_at, limit], |r| { | ||
| Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?)) | ||
| })? | ||
| .collect::<std::result::Result<Vec<_>, _>>()?; | ||
| Ok(rows) | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
set -euo pipefail
printf '\n## Files\n'
git ls-files 'src/openhuman/orchestration/store.rs' 'src/openhuman/**/store.rs' 'src/openhuman/**/mod.rs' | sed 's#^`#-` #'
printf '\n## Search related symbols\n'
rg -n "list_unreviewed_compressed|set_review_cursor|review cursor|compressed_history|created_at" src/openhuman/orchestration/store.rs src/openhuman -g '!**/target/**'
printf '\n## store.rs around relevant lines\n'
sed -n '1,260p' src/openhuman/orchestration/store.rs
printf '\n--- middle ---\n'
sed -n '260,620p' src/openhuman/orchestration/store.rsRepository: tinyhumansai/openhuman
Length of output: 50379
🏁 Script executed:
set -euo pipefail
printf '\n## Schema/search for compressed_history\n'
rg -n "CREATE TABLE.*compressed_history|compressed_history" src/openhuman -g '!**/target/**'
printf '\n## Search for insert into compressed_history\n'
rg -n "INSERT INTO compressed_history|compressed_history" src/openhuman -g '!**/target/**'
printf '\n## Search for created_at generation format\n'
rg -n "created_at|strftime|now\\(|Utc::now|SystemTime|timestamp" src/openhuman -g '!**/target/**'Repository: tinyhumansai/openhuman
Length of output: 50379
🏁 Script executed:
set -euo pipefail
sed -n '1,120p' src/openhuman/orchestration/store.rs
printf '\n---\n'
sed -n '380,560p' src/openhuman/orchestration/store.rs
printf '\n---\n'
sed -n '180,270p' src/openhuman/orchestration/ops.rs
printf '\n---\n'
sed -n '760,790p' src/openhuman/orchestration/store.rsRepository: tinyhumansai/openhuman
Length of output: 16063
🏁 Script executed:
set -euo pipefail
sed -n '800,860p' src/openhuman/orchestration/store.rsRepository: tinyhumansai/openhuman
Length of output: 2416
🏁 Script executed:
set -euo pipefail
printf '## src/openhuman/orchestration/store.rs\n'
sed -n '1,90p' src/openhuman/orchestration/store.rs
printf '\n## src/openhuman/orchestration/store.rs (compressed_history + cursor)\n'
sed -n '380,560p' src/openhuman/orchestration/store.rs
printf '\n## src/openhuman/orchestration/ops.rs (use of compressed history)\n'
sed -n '184,260p' src/openhuman/orchestration/ops.rs
printf '\n## tests around compressed history\n'
sed -n '760,860p' src/openhuman/orchestration/store.rsRepository: tinyhumansai/openhuman
Length of output: 17191
🏁 Script executed:
set -euo pipefail
rg -n -C 4 "insert_compressed\\(|created_at: .*to_rfc3339|to_rfc3339\\(\\)" src/openhuman/orchestration/ops.rs src/openhuman/orchestration/store.rs
printf '\n--- around compressed write path ---\n'
sed -n '620,720p' src/openhuman/orchestration/ops.rsRepository: tinyhumansai/openhuman
Length of output: 13380
Use a composite review cursor for compressed history. list_unreviewed_compressed pages only on created_at; if two rows land on the same timestamp, the later one can be skipped when set_review_cursor advances. Order and cursor by created_at plus a unique tie-breaker like cycle_id to make the scan safe.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/openhuman/orchestration/store.rs` around lines 527 - 544,
`list_unreviewed_compressed` currently pages only by `created_at`, so rows
sharing the same timestamp can be skipped when the review cursor advances.
Update the compressed-history scan to use a composite cursor with `created_at`
plus a unique tie-breaker such as `cycle_id`, and make the query order by both
fields consistently. Also adjust the cursor advancement logic around
`set_review_cursor` so it records both parts of the composite position and
resumes safely without missing same-timestamp rows.
|
Heads-up on the red CI: Root cause:
One-commit fix (register both in Left the two P1 findings (graph/mod.rs:232 quick-reply routing; ops.rs:407 Master seq=0 cursor) untouched — they're logic bugs the current tests pass despite, so they need your context. |
…e-id seam note Addresses @M3gA-Mind review: - store.rs: CREATE UNIQUE INDEX IF NOT EXISTS under the existing name is a SQLite no-op on DBs that already have the non-unique index (tinyhumansai#4430), so uniqueness never took on already-created stores. Drop the legacy index, de-dupe any pre-existing race rows (keep earliest per key so the UNIQUE build can't fail), and create the unique index under a new name — all idempotent on execute_batch. Adds a test simulating a legacy DB → migration → dedupe + duplicate-seq rejection. - state.rs: document the cycle_id format-change migration seam (an in-flight cycle at the upgrade boundary may append one extra timeline entry; Beta-gated, not corruption).
…o tinyagents migration Resolves the conflict in tinyagents/topology.rs (kept both upstream's new orchestration:wake graph and our branch's delegation/workflow_runs/subagent/ researcher/spawn_parallel topologies) and the semantic breaks where upstream's tinyhumansai#4430 depended on symbols this branch changed: - agent_registry/agents/loader.rs: wrap the new frontend_agent/reasoning_agent graph_fn entries in Some(...) — our 08.4 work made graph_fn Option<fn()->AgentGraph>. - orchestration/{graph/mod.rs,ops.rs,graph/state.rs}: port the wake graph off the retired SqlRunLedgerCheckpointer (deleted in 04.3) to the crate SqliteCheckpointer at a dedicated orchestration_checkpoints.db, mirroring agent_orchestration/delegation.rs. Merged tree compiles clean; 607 tests green across the touched areas. Claude-Session: https://claude.ai/code/session_0137dT9i1vLjeUQZ3mj8ZAct
…e-id seam note Addresses @M3gA-Mind review: - store.rs: CREATE UNIQUE INDEX IF NOT EXISTS under the existing name is a SQLite no-op on DBs that already have the non-unique index (tinyhumansai#4430), so uniqueness never took on already-created stores. Drop the legacy index, de-dupe any pre-existing race rows (keep earliest per key so the UNIQUE build can't fail), and create the unique index under a new name — all idempotent on execute_batch. Adds a test simulating a legacy DB → migration → dedupe + duplicate-seq rejection. - state.rs: document the cycle_id format-change migration seam (an in-flight cycle at the upgrade boundary may append one extra timeline entry; Beta-gated, not corruption).
…tinyagents migration + versioned world-diff migration Rebased onto current main (c43f796) which merged tinyhumansai#4399 (finish TinyAgents migration) after tinyhumansai#4430 — leaving main red (Rust Quality) from two semantic conflicts this repairs: - agent_registry loader: BuiltinAgent.graph_fn is now Option<fn()->AgentGraph>; wrap frontend_agent/reasoning_agent in Some(...) (E0308). - orchestration graph: tinyhumansai#4399 retired SqlRunLedgerCheckpointer for the tinyagents crate SqliteCheckpointer (graph_checkpoints.db). Migrate graph/mod.rs + the ops resume test to SqliteCheckpointer::open, mirroring delegation.rs (E0432). Also addresses CodeRabbit on the world-diff unique index: move it into a user_version-gated one-time migrate() (was CREATE UNIQUE INDEX IF NOT EXISTS — a no-op on already-created stores), de-dupe legacy race rows, and reconcile terminal_state so it can't point at a deleted duplicate's mutation. Test extended with distinct mutations + stale terminal_state + user_version assertions.
…ansai#4430 squash-merge The TinyAgents migration (tinyhumansai#4249) squash-merged on top of tinyhumansai#4430's subconscious orchestration layer, but the pre-integration merge state landed: tinyhumansai#4430's wake graph still references SqlRunLedgerCheckpointer (deleted by the migration in 04.3) and the new frontend_agent/reasoning_agent registrations pass a bare graph_fn while the migration made that field Option<fn() -> AgentGraph>. main does not compile (E0432 unresolved import + E0308 mismatched types). Restore the fixes that never reached main: - agent_registry/agents/loader.rs: wrap the two orchestration graph_fn entries in Some(...). - orchestration/{graph/mod.rs, ops.rs test, graph/state.rs doc}: port the wake graph off the retired SqlRunLedgerCheckpointer to the crate SqliteCheckpointer at a dedicated orchestration_checkpoints.db, mirroring agent_orchestration/ delegation.rs. main + this fix compiles clean. Claude-Session: https://claude.ai/code/session_0137dT9i1vLjeUQZ3mj8ZAct
…ce mock tinyhumansai#4399 raised RUNTIME_SNAPSHOT_TTL 2s -> 10s to fix a rebuild-stampede perf bug, but app_state_snapshot_degrades_runtime_service_status_failures ages the cache out with a 2.1s sleep — fine for the old 2s TTL, now < the 10s one, so the test reads a stale cached snapshot from a prior test and never sees the injected 'forced status failure' (asserts service degrades to Unknown). Deterministic; only surfaced now because main (with tinyhumansai#4399) doesn't compile until tinyhumansai#4431 repairs the tinyhumansai#4399/tinyhumansai#4430 conflict, so this is the first branch to actually run it. Fix: when OPENHUMAN_SERVICE_MOCK is active (a test-only env hook production service status already honors), never serve from or write to the process-global runtime cache — the mock's state changes between calls, so caching masks the injected value and poisons later non-mocked reads. Full config_auth_app_state_connectivity_e2e binary: 50 passed.
With the build restored, two pre-existing failures from the tinyhumansai#4249/tinyhumansai#4430 merge seam now run: - mcp_server/resources.rs: tinyhumansai#4430 added frontend_agent + reasoning_agent to the agent BUILTINS but not to RESOURCE_CATALOG, so catalog_mirrors_builtins and read_resource_returns_content_for_each_subagent panicked. Add both entries (using each agent's prompt.md). - app_state/ops.rs: the tinyhumansai#4249 profiling fix raised RUNTIME_SNAPSHOT_TTL to 10s, breaking app_state_snapshot_degrades_runtime_service_status_failures, which ages the process-global runtime cache out with a 2100ms sleep. Revert the TTL to 2s — the single-flight gate is the real stampede cure; the TTL only sets how often a coalesced rebuild runs, so 2s keeps status fresh and honors the test's age-out contract with no perf regression. Verified: mcp_server::resources 9/9, app_state::ops 17/17, and the e2e app_state_snapshot_degrades... 1/1 against the mock backend. Claude-Session: https://claude.ai/code/session_0137dT9i1vLjeUQZ3mj8ZAct
…n review fixes Rebased onto current main (8aab529), which already carries the tinyhumansai#4399/tinyhumansai#4430 compile reconciliation (graph_fn Some, SqliteCheckpointer migration, MCP catalog) via tinyhumansai#4433 — so this is slimmed to the fixes main still lacks: - app_state: bypass the runtime-snapshot cache when OPENHUMAN_SERVICE_MOCK is active. tinyhumansai#4399 raised RUNTIME_SNAPSHOT_TTL 2s->10s, so the mock-service e2e (which ages the cache with a 2.1s sleep) reads a stale snapshot and never sees the injected failure. Bypassing read+write under the mock fixes it (full config_auth_app_state_connectivity_e2e binary: 50 passed). - orchestration/ops: split steering out of seed_state into apply_cycle_steering, invoked only after the has_new_work guard, so no-op wakes don't consume a cycle tick and prematurely expire steering (CodeRabbit). - orchestration/store: user_version-gated world-diff migration — drop legacy non-unique index, de-dupe race rows, unique index, reconcile terminal_state (M3gA-Mind + CodeRabbit) + test. - orchestration/graph/state: agent-scoped cycle_id (avoid cross-peer collision) + migration-seam note (Codex + M3gA-Mind). - orchestration/schemas: ingest-health MAX(last_message_at) excludes pinned master/subconscious windows (CodeRabbit). - frontend: orchestration socket listeners via socketService.on/off (queues through startup/reconnect) + messages Retry -> refresh() (CodeRabbit/Codex). - docs: orchestration.md fenced-block language (markdownlint).
Summary
tinyagentsgraph.normalize → frontend(1) → execute → compress → world_diff → frontend(2) → send_dm → context_guard → done, checkpointed under threadorchestration:<session_id>; two-pass Quick-LLM front end + reasoning core with sub-agent spawning, 20:1 compression, append-only world diff, and 80–90% context-eviction guard.SubconsciousEnginetick reviews compressed history + the cumulative world diff and emits short steering directives injected into later reasoning prompts — no channels, no external effects.openhuman.orchestration_*internal controllers + a rewiredTinyPlaceOrchestrationTabreading real store classification, live socket updates, and a Master composer.orchestration.statuscursor-lag/last-error, and a log-leak guard.Problem
Wrapped coding-agent sessions had no way to be coordinated by the owner agent: stage 3 only ingested their DMs into a store. There was no autonomous reply loop, no reasoning/memory pipeline, no offline reflection, and the Brain tab bucketed raw envelopes with string heuristics rather than real classification.
Solution
OrchestrationStateflows through a single command-routing graph; every behaviour-bearing node is bundled behind one injectedOrchestrationRuntimeso the graph mechanics are hermetically unit-testable with a stub while production wires the real agents/store/memory.seed_stateloads the current directive into state at cycle start.orchestration:messagesocket bridge power the tab; the frontend is a presentational component over auseOrchestrationChatshook.cycle_idfor resume-idempotent store writes; strict 20:1 compression budget with a 200-token floor; loop-continuity backstop onmax_supersteps.Submission Checklist
docs/TEST-COVERAGE-MATRIX.mdfeature-row schema exists for this domain; behaviour is covered by the tests above.[orchestration]config block).docs/plans/subconscious-orchestration/.Impact
[orchestration]config block (enabled,debounce_ms,max_supersteps,message_window,context_evict_threshold,subagent_concurrency).orchestration, renderer-only) and two built-in agents (frontend_agent,reasoning_agent).Related
tests/json_rpc_e2e.rsharness + mock tiny.place relay, WDIO Linux lane, and the coverage-gate CI wiring (stage-8 CI-infra items); richer per-tool execution trace for compression; liveAgentTurnUsage-based context utilization.AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
Validation Run
pnpm --filter openhuman-app format:checkpnpm typecheckcargo test --lib openhuman::orchestration(48 passed),pnpm test TinyPlaceOrchestrationTab(8 passed)cargo check --manifest-path Cargo.tomlgreen; rustfmt appliedapp/src-tauriRust changed (socketio.rs bridge is in the root core crate)Validation Blocked
command:N/Aerror:N/Aimpact:N/ABehavior Changes
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Localization