14 KiB
description, icon
| description | icon |
|---|---|
| Implementation plan for the conscious loop that extracts actionable items from the memory tree. | circle-dot |
Conscious Loop — Implementation Plan
Context
The app has a memory layer (TinyHumans Neocortex) where skills (gmail, telegram, notion, etc.) store synced data. Currently this data sits passively — it's only recalled when the user sends a chat message. The Conscious Loop is a periodic background process that proactively digests all skill memory into structured actionable items, matching the ActionableItem format already used in the Intelligence UI (src/components/intelligence/mockData.ts). This replaces mock data with real, LLM-extracted intelligence.
Flow: Recall all skill memory → LLM extracts actionables → Log response → Insert back into memory under conscious namespace.
Files to Create
1. src-tauri/ai/CONSCIOUS_LOOP.md — LLM prompt template
The structured prompt that tells the LLM how to extract actionable items from recalled memory. Loaded at runtime via find_ai_directory() (same pattern as SOUL.md).
2. src-tauri/src/commands/conscious_loop.rs — Core implementation (~250 lines)
Structs:
ConsciousLoopStartedEvent— emitted when a run beginsConsciousLoopCompletedEvent— emitted on success (includes actionable count, duration)ConsciousLoopErrorEvent— emitted on failure
Functions:
conscious_loop_run (Tauri command)
- Params:
app: AppHandle,auth_token,backend_url,model,memory_state: State<MemoryState> - Spawns
conscious_loop_run_inneras background task, returns immediately - Enables manual triggering from frontend
conscious_loop_run_inner (core logic)
- Emit
conscious_loop:started - Get skill IDs — call
engine.all_tools()to obtain the set of activeskill_ids(same source used bychat_send_inner). No separate API call needed — each skill ID is directly its memory namespace - Recall memory for each skill via
memory_client.recall_skill_context(&skill_id, &skill_id, 10). Collect asVec<(skill_id, context_text)>. Skip skills that returnNone - Load prompt from
ai/CONSCIOUS_LOOP.mdviafind_ai_directory(app). Hardcoded fallback if file missing - Build messages —
[{role: "system", content: prompt}, {role: "user", content: assembled_contexts}] - Call inference — POST to
{backend_url}/openai/v1/chat/completionswith Bearer auth, 120s timeout (samereqwestpattern aschat_send_inner) - Log full response —
log::info!("[conscious_loop] LLM response: {}", response) - Parse JSON array — deserialize into
Vec<ExtractedActionable>(title, description, source, priority, actionable, requires_confirmation, source_label, has_complex_action) - Insert into memory — for each item, call
memory_client.store_skill_sync("conscious", "actionables", &title, &json_content, ...). Use deterministicdocument_id(hash of title+source) for deduplication - Emit
conscious_loop:completed
conscious_loop_timer (periodic runner)
- Spawned from
lib.rssetup, runs ontokio::time::interval(Duration::from_secs(300))(5 min) - 60s initial delay for skills to boot and memory to initialize
- Checks: memory client initialized? auth token present? If not, skip silently
- Calls
conscious_loop_run_inneron each tick - Uses
gpt-4o-minias default model (configurable viaOPENHUMAN_CONSCIOUS_MODELenv var)
Error handling:
- Memory client not initialized → skip (timer) or return Err (manual command)
- No skill IDs found → emit completed with 0 items (not an error)
- Individual skill recall fails → log warning, skip, continue
- Inference call fails → emit
conscious_loop:error - JSON parse fails → log raw response, emit error
- Individual memory insert fails → log warning, continue with remaining items
Files to Modify
3. src-tauri/src/commands/memory.rs
- Make
extract_namespaces_from_documentspub(crate)(currently privatefn)
4. src-tauri/src/commands/mod.rs
- Add
pub mod conscious_loop; - Add
pub use conscious_loop::*;
5. src-tauri/src/lib.rs
- Desktop handler list (~line 1088): Add
conscious_loop_runcommand - Mobile handler list (~line 1095): Add no-op stub
- Setup block (~line 928): Spawn
conscious_loop_timerafter memory state is registered:let app_for_conscious = app.handle().clone(); tauri::async_runtime::spawn(async move { commands::conscious_loop::conscious_loop_timer(app_for_conscious).await; });
Key Design Decisions
| Decision | Rationale |
|---|---|
| Rust-side timer (not frontend setInterval) | App runs in tray mode where webview is hidden. Tokio interval survives this. Matches watch_daemon_health_file pattern |
recall_skill_context (via engine.all_tools()) |
Consistent with chat_send_inner. No extra API call — skill IDs are already available from the runtime. integration_id is passed as skill_id (same convention) |
gpt-4o-mini default |
Background summarization, not conversational. Faster + cheaper. Configurable via env var |
| 5-minute interval | Frequent enough for time-sensitive items, conservative on tokens |
| Deterministic document_id | Hash of title+source enables dedup on repeated runs |
conscious namespace |
Clean separation from skill data. Avoids polluting skill namespaces |
Event Protocol (Rust → Frontend)
| Event | Payload | Purpose |
|---|---|---|
conscious_loop:started |
{ run_id, timestamp, namespaces[] } |
UI can show loading state |
conscious_loop:completed |
{ run_id, actionable_count, duration_ms } |
UI can refresh Intelligence view |
conscious_loop:error |
{ run_id, message, error_type } |
UI can show error indicator |
ActionableItem Output Format
The LLM will output items matching this structure (from src/types/intelligence.ts):
[
{
"title": "Reply to 2 critical emails expecting response within 24hrs",
"description": "Messages from john@coinbase.com and sarah@ethereum.org about partnership proposals",
"source": "email",
"priority": "critical",
"actionable": true,
"requires_confirmation": false,
"has_complex_action": true,
"source_label": "Gmail"
}
]
Source values: email | calendar | telegram | ai_insight | system | trading | security
Priority values: critical | important | normal
CONSCIOUS_LOOP.md Prompt (for src-tauri/ai/)
# Conscious Loop — Actionable Extraction
You are the conscious awareness layer of OpenHuman. You periodically review all
memory contexts from the user's connected integrations and extract actionable
items that deserve attention.
## Your Task
Analyze the recalled memory contexts provided below. For each context, identify
items that are:
1. **Time-sensitive** — deadlines, expiring offers, meetings, scheduled events
2. **Requires response** — unanswered emails, pending messages, open requests
3. **Opportunity** — insights, patterns, or suggestions the user may benefit from
4. **Risk/Alert** — security issues, anomalies, overdue tasks, budget warnings
## Output Format
Return a JSON array of actionable items. Each item must have this exact structure:
{
"title": "Short descriptive title (under 80 chars)",
"description": "1-2 sentence explanation with context",
"source": "email|calendar|telegram|ai_insight|system|trading|security",
"priority": "critical|important|normal",
"actionable": true,
"requires_confirmation": false,
"has_complex_action": false,
"source_label": "Human-readable source name (e.g. Gmail, Telegram, Notion)"
}
## Rules
- Return ONLY the JSON array, no markdown fences, no commentary
- Deduplicate: if the same item appears in multiple sources, merge into one
- Limit to 20 items maximum per run — prioritize the most important
- Use "ai_insight" as source when the item is a synthesized observation
- Use "system" for maintenance, sync status, or technical alerts
- Map integration sources: gmail -> "email", telegram -> "telegram", notion -> "system", google_calendar -> "calendar"
- Set priority "critical" only for truly urgent items (expiring today, security breach)
- Set priority "important" for items needing attention within 24-48 hours
- Set "has_complex_action" to true when the item requires multi-step user action
- Set "requires_confirmation" to true when the item involves financial transactions or irreversible actions
- If no actionable items are found, return an empty array: []
Verification
- Compile check:
cargo check --manifest-path src-tauri/Cargo.toml - Rust formatting:
cargo fmt --manifest-path src-tauri/Cargo.toml - Manual test: Call
invoke('conscious_loop_run', { authToken, backendUrl, model })from frontend console - Log verification: Check Rust logs for
[conscious_loop]entries showing recall data, LLM response, and insert results - Memory verification: Call
invoke('memory_list_documents', { namespace: 'conscious' })to see stored actionables - Timer verification: Watch logs for periodic
[conscious_loop]entries every 5 minutes after app startup
Architecture Diagram
┌─────────────────────────────────────────────────────┐
│ Rust Backend │
│ │
│ ┌──────────────────┐ every 5 min │
│ │ conscious_loop_ │◄──────────────────┐ │
│ │ timer() │ │ │
│ └────────┬──────────┘ tokio::interval │
│ │ │ │
│ ▼ │ │
│ ┌──────────────────┐ │ │
│ │ conscious_loop_ │ │
│ │ run_inner() │ │
│ │ │ │
│ │ 1. engine.all_tools() ──► get skill_ids │
│ │ 2. recall_skill_context() per skill_id │
│ │ 3. Load CONSCIOUS_LOOP.md prompt │
│ │ 4. POST /openai/v1/chat/completions │
│ │ 5. Log full LLM response │
│ │ 6. Parse JSON → Vec<ExtractedActionable> │
│ │ 7. store_skill_sync("conscious", "actionables") │
│ │ 8. Emit events to frontend │
│ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ TinyHumans API │ (recall + insert) │
│ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Backend LLM │ (inference) │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────┘
│
▼ Tauri events
┌─────────────────────────────────────────────────────┐
│ React Frontend │
│ │
│ listen('conscious_loop:completed') │
│ → Refresh Intelligence UI with real actionables │
└─────────────────────────────────────────────────────┘