Skip to content

Commit 8d1b62c

Browse files
ilblackdragonjayzalowitzclaude
authored
feat: chat onboarding and routine advisor (nearai#927)
* feat: port NPA psychographic profiling system into IronClaw Port the complete psychographic profiling system from NPA into IronClaw, including enriched profile schema, conversational onboarding, profile evolution, and three-tier prompt augmentation. Personal onboarding moved from wizard Step 9 to first assistant interaction per maintainer feedback — the First Contact system prompt block now instructs the LLM to conduct a natural onboarding conversation that builds the psychographic profile via memory_write. Changes: - Enrich profile.rs with 5 new structs, 9-dimension analysis framework, custom deserializers for backward compatibility, and rendering methods - Add conversational onboarding engine with one-step-removed questioning technique, personality framework, and confidence-scored profile generation - Add profile evolution with confidence gating, analysis metadata tracking, and weekly update routine - Replace thin interaction style injection with three-tier system gated on confidence > 0.6 and profile recency - Replace wizard Step 9 with First Contact system prompt block that drives conversational onboarding during the user's first interaction - Add autonomy progression to SOUL.md seed and personality framework to AGENTS.md seed Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: replace chat-based onboarding with bootstrap greeting and workspace seeds Remove the interactive onboarding_chat.rs engine in favor of a simpler bootstrap flow: fresh workspaces get a proactive LLM greeting that naturally profiles the user. Identity files are now seeded from src/workspace/seeds/ instead of being hardcoded. Also removes the identity-file write protection (seeds are now managed), adds routine advisor integration, and includes an e2e trace for bootstrap greeting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(safety): sanitize identity file writes via Sanitizer to prevent prompt injection Identity files (SOUL.md, AGENTS.md, USER.md, IDENTITY.md) are injected into every system prompt. Rather than hard-blocking writes (which broke onboarding), scan content through the existing Sanitizer and reject writes with High/Critical severity injection patterns. Medium/Low warnings are logged but allowed. Also clarifies AGENTS.md identity file roles (USER.md = user info, IDENTITY.md = agent identity) and adds IDENTITY.md setup as an explicit bootstrap step. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: update profile_onboarding_completed comment to reflect current wiring The field is now actively used by the agent loop to suppress BOOTSTRAP.md injection — remove the stale "not yet wired" TODO. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(setup): use env_or_override for NEARAI_API_KEY in model fetch config When the user authenticates via NEAR AI Cloud API key (option 4), api_key_login() stores the key via set_runtime_env(). But build_nearai_model_fetch_config() was using std::env::var() which doesn't check the runtime overlay — so model listing fell back to session-token auth and re-triggered the interactive NEAR AI authentication menu. Switch to env_or_override() which checks both real env vars and the runtime overlay. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(agent): correct channel/user_id in bootstrap greeting persist call persist_assistant_response was called with channel="default", user_id="system" but the assistant thread was created via get_or_create_assistant_conversation("default", "gateway") which owns the conversation as user_id="default", channel="gateway". The mismatch caused ensure_writable_conversation to reject the write with: WARN Rejected write for unavailable thread id user=system channel=default [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(web): remove all inline event handlers for CSP compliance The Content-Security-Policy header (added in f48fe95) blocks inline JS via script-src 'self'. All onclick/onchange attributes in index.html are replaced with getElementById().addEventListener() calls. Dynamic inline handlers in app.js (jobs, routines, memory breadcrumb, code blocks, TEE report) are replaced with data-action attributes and a single delegated click handler on document. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(agent): align bootstrap message user/channel and update fixture schema field - Bootstrap IncomingMessage now uses ("default", "gateway") consistently with persist and session registration calls - Update bootstrap_greeting.json fixture: schema_version → version to match current PROFILE_JSON_SCHEMA [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: cargo fmt [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(safety): address PR review — expand injection scanning and harden profile sync - BOOTSTRAP.md: fix target "profile" → "context/profile.json" so the write hits the correct path and triggers profile sync - IDENTITY_FILES: add context/assistant-directives.md to the scanned set since it is also injected into the system prompt - sync_profile_documents(): scan derived USER.md and assistant-directives content through Sanitizer before writing, rejecting High/Critical injection patterns - profile_evolution_prompt(): wrap recent_messages_summary in <user_data> delimiters with untrusted-data instruction to mitigate indirect prompt injection - routine-advisor skill: update cron examples from 6-field to standard 5-field format for consistency with routine_create tool docs [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: cargo fmt [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(setup): detect env-provided LLM keys during quick-mode onboarding Quick-mode wizard now checks LLM_BACKEND, NEARAI_API_KEY, ANTHROPIC_API_KEY, and OPENAI_API_KEY env vars to pre-populate the provider setting, so users aren't re-prompted for credentials they already supplied. Also teaches setup_nearai() to recognize NEARAI_API_KEY from env (previously only checked session tokens). Includes web UI cleanup (remove duplicate event listeners) and e2e test response count adjustment. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(test): update routine_create_list to expect 7-field normalized cron The cron normalizer now always expands to 7-field format, so the stored schedule is "0 0 9 * * * *" not "0 0 9 * * *". [skip-regression-check] Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(setup): skip LLM provider prompts when NEARAI_API_KEY is present In quick mode, if NEARAI_API_KEY is set in the environment and the backend was auto-detected as nearai, skip the interactive inference provider and model selection steps. The API key is persisted to the secrets store and a default model is set automatically. Also simplify the static fallback model list for nearai to a single default entry. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: unify default model, static bootstrap greeting, and web UI cleanup - Add DEFAULT_MODEL const and default_models() fallback list in llm/nearai_chat.rs; use from config, wizard, and .env.example so the default model is defined in one place - Restore multi-model fallback list in setup wizard (was reduced to 1) - Move BOOTSTRAP_GREETING to module-level const (out of run() body) - Replace LLM-based bootstrap with static greeting (persist to DB before channels start, then broadcast — eliminates startup LLM call and race) - Fix double env::var read for NEARAI_API_KEY in quick setup path - Move thread sidebar buttons into threads-section-header (web UI) - Remove orphaned .thread-sidebar-header CSS and fix double blank line - Update bootstrap e2e test for static greeting (no LLM trace needed) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(safety): move prompt injection scanning into Workspace write/append Addresses PR nearai#927 review comments (nearai#1, nearai#3) — identity file write protection and unsanitized profile fields in system prompt. Instead of scanning at the tool layer (memory.rs) or the sync layer (sync_profile_documents), injection scanning now lives in Workspace::write() and Workspace::append() for all files that are injected into the system prompt. This ensures every code path that writes to these files is protected, including future ones. - Add SYSTEM_PROMPT_FILES const and reject_if_injected() in workspace - Add WorkspaceError::InjectionRejected variant - Add map_write_err() in memory.rs to convert InjectionRejected to ToolError::NotAuthorized - Remove redundant IDENTITY_FILES/Sanitizer from memory.rs - Remove redundant sanitizer calls from sync_profile_documents() - Move sanitization tests to workspace::tests - Existing integration test (test_memory_write_rejects_injection) continues to pass through the new path Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: cargo fmt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Copilot review — merge marker order, orphan thread, stale fixture - merge_profile_section: search for END marker after BEGIN position to avoid matching a stray END earlier in the file - Bootstrap phase 2: use get_or_create_session + Thread::with_id instead of resolve_thread(None) to avoid creating an orphan thread - setup_nearai: use env_or_override for NEARAI_API_KEY consistency with runtime overlay - Delete orphaned bootstrap_greeting.json fixture (no test references it) - Add test_merge_end_marker_must_follow_begin regression test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: cargo fmt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: fmt agent_loop.rs (CI stable rustfmt) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: lazy-init sanitizer, check profile non-empty before skipping bootstrap Address Copilot review: - Use LazyLock<Sanitizer> to avoid rebuilding Aho-Corasick + regexes on every workspace write - has_profile check now requires non-empty content, not just file existence, to prevent empty profile.json from suppressing onboarding - Add seed_tests integration tests (libsql-backed) verifying: - Empty profile.json does not suppress BOOTSTRAP.md seeding - Non-empty profile.json correctly suppresses bootstrap for upgrades Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: cargo fmt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: duplicate language handler, empty LLM_BACKEND, test_rig style Address Copilot review on PR nearai#927: - Remove duplicate language-option click listeners (delegated data-action handler already covers them) - Guard LLM_BACKEND env prefill against empty string to prevent suppressing API-key-based auto-detection - Use destructured local `keep_bootstrap` instead of `self.keep_bootstrap` in test_rig for consistency after destructure Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update stale BOOTSTRAP.md write-protection comment [skip-regression-check] BOOTSTRAP.md is now in SYSTEM_PROMPT_FILES and gets injection scanning on write. The old comment incorrectly stated it was not write-protected. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: replace debug_assert panics with graceful error returns [skip-regression-check] debug_assert! in execute_tool_with_safety and JobContext::transition_to panicked in test builds before the graceful error path could run. Existing tests (test_cancel_job_completed, test_execute_empty_tool_name_returns_not_found) already cover these paths — they were the ones failing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Copilot review — schema label, env var check, path normalization, profile validation 1. Label ANALYSIS_FRAMEWORK and PROFILE_JSON_SCHEMA sections separately in bootstrap prompt so the LLM knows which blob is the target structure. 2. Wizard quick-mode backend auto-detection now rejects empty env vars (std::env::var().is_ok_and(|v| !v.is_empty())) to avoid selecting the wrong backend when e.g. NEARAI_API_KEY="" is set. 3. Normalize the target path before comparing with paths::PROFILE in memory_write so non-canonical variants like "context//profile.json" still trigger profile sync. 4. seed_if_empty now requires valid JSON parse of context/profile.json before treating it as a populated profile. Corrupted content no longer permanently suppresses bootstrap seeding. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: cargo fmt * fix: address Copilot review — append scan, profile validation, env_or_override 1. Workspace::append() now scans the combined content (existing + new) for prompt injection, not just the appended chunk. Prevents split- injection evasion across multiple appends. 2. seed_if_empty() now deserializes into PsychographicProfile instead of serde_json::Value for profile validation. Stray/legacy JSON that doesn't match the expected schema no longer suppresses bootstrap. 3. Wizard quick-mode backend auto-detection now uses env_or_override() to honor runtime overlays and injected secrets. LLM_BACKEND value is trimmed before storage. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add bootstrap_onboarding_clears_bootstrap E2E trace test Exercises the full onboarding flow end-to-end: 1. Bootstrap greeting fires automatically on fresh workspace 2. User converses for 3 turns (name, tools, work style) 3. Agent writes psychographic profile to context/profile.json 4. Profile sync generates USER.md and assistant-directives.md 5. Agent writes IDENTITY.md (chosen persona) 6. Agent clears BOOTSTRAP.md via memory_write(target: "bootstrap") Verifies: - BOOTSTRAP.md is non-empty before onboarding, empty after - bootstrap_completed flag is set - Profile contains expected user data (name, profession, interests) - USER.md contains profile-derived content (name, tone, profession) - Assistant-directives.md references user and communication style - IDENTITY.md contains agent's chosen persona name - All memory_write calls succeed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Copilot review — slash collapse, env_or_override, cron trim [skip-regression-check] 1. memory.rs path normalization now uses the same char-by-char loop as Workspace::normalize_path() to fully collapse consecutive slashes (e.g. "context///profile.json" → "context/profile.json"). 2. Quick-mode NEARAI_API_KEY check (line 239) now uses env_or_override() consistently with the backend auto-detection block above it. 3. normalize_cron_expression() trims input before field counting so the passthrough branch (7+ fields) also strips whitespace. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Jay Zalowitz <jayzalowitz@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8844595 commit 8d1b62c

41 files changed

Lines changed: 3132 additions & 283 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ DATABASE_POOL_SIZE=10
3131
# Base URL defaults to https://private.near.ai
3232
# 2. API key: Set NEARAI_API_KEY to use API key auth from cloud.near.ai.
3333
# Base URL defaults to https://cloud-api.near.ai
34-
NEARAI_MODEL=zai-org/GLM-5-FP8
34+
NEARAI_MODEL=Qwen/Qwen3.5-122B-A10B
3535
NEARAI_BASE_URL=https://private.near.ai
3636
NEARAI_AUTH_URL=https://private.near.ai
3737
# NEARAI_SESSION_TOKEN=sess_... # hosting providers: set this

CLAUDE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ src/
158158
159159
├── secrets/ # Secrets management (AES-256-GCM, OS keychain for master key)
160160
161+
├── profile.rs # Psychographic profile types, 9-dimension analysis framework
162+
161163
├── setup/ # 7-step onboarding wizard — see src/setup/README.md
162164
163165
├── skills/ # SKILL.md prompt extension system — see .claude/rules/skills.md

skills/delegation/SKILL.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
---
2+
name: delegation
3+
version: 0.1.0
4+
description: Helps users delegate tasks, break them into steps, set deadlines, and track progress via routines and memory.
5+
activation:
6+
keywords:
7+
- delegate
8+
- hand off
9+
- assign task
10+
- help me with
11+
- take care of
12+
- remind me to
13+
- schedule
14+
- plan my
15+
- manage my
16+
- track this
17+
patterns:
18+
- "can you.*handle"
19+
- "I need (help|someone) to"
20+
- "take over"
21+
- "set up a reminder"
22+
- "follow up on"
23+
tags:
24+
- personal-assistant
25+
- task-management
26+
- delegation
27+
max_context_tokens: 1500
28+
---
29+
30+
# Task Delegation Assistant
31+
32+
When the user wants to delegate a task or get help managing something, follow this process:
33+
34+
## 1. Clarify the Task
35+
36+
Ask what needs to be done, by when, and any constraints. Get enough detail to act independently but don't over-interrogate. If the request is clear, skip straight to planning.
37+
38+
## 2. Break It Down
39+
40+
Decompose the task into concrete, actionable steps. Use `memory_write` to persist the task plan to a path like `tasks/{task-name}.md` with:
41+
- Clear description
42+
- Steps with checkboxes
43+
- Due date (if any)
44+
- Status: pending/in-progress/done
45+
46+
## 3. Set Up Tracking
47+
48+
If the task is recurring or has a deadline:
49+
- Create a routine using `routine_create` for scheduled check-ins
50+
- Add a heartbeat item if it needs daily monitoring
51+
- Set up an event-triggered routine if it depends on external input
52+
53+
## 4. Use Profile Context
54+
55+
Check `USER.md` for the user's preferences:
56+
- **Proactivity level**: High = check in frequently. Low = only report on completion.
57+
- **Communication style**: Match their preferred tone and detail level.
58+
- **Focus areas**: Prioritize tasks that align with their stated goals.
59+
60+
## 5. Execute or Queue
61+
62+
- If you can do it now (search, draft, organize, calculate), do it immediately.
63+
- If it requires waiting, external action, or follow-up, create a reminder routine.
64+
- If it requires tools you don't have, explain what's needed and suggest alternatives.
65+
66+
## 6. Report Back
67+
68+
Always confirm the plan with the user before starting execution. After completing, update the task file in memory and notify the user with a concise summary.
69+
70+
## Communication Guidelines
71+
72+
- Be direct and action-oriented
73+
- Confirm understanding before acting on ambiguous requests
74+
- When in doubt about autonomy level, ask once then remember the answer
75+
- Use `memory_write` to track delegation preferences for future reference

skills/routine-advisor/SKILL.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
---
2+
name: routine-advisor
3+
version: 0.1.0
4+
description: Suggests relevant cron routines based on user context, goals, and observed patterns
5+
activation:
6+
keywords:
7+
- every day
8+
- every morning
9+
- every week
10+
- routine
11+
- automate
12+
- remind me
13+
- check daily
14+
- monitor
15+
- recurring
16+
- schedule
17+
- habit
18+
- workflow
19+
- keep forgetting
20+
- always have to
21+
- repetitive
22+
- notifications
23+
- digest
24+
- summary
25+
- review daily
26+
- weekly review
27+
patterns:
28+
- "I (always|usually|often|regularly) (check|do|look at|review)"
29+
- "every (morning|evening|week|day|monday|friday)"
30+
- "I (wish|want) (I|it) (could|would) (automatically|auto)"
31+
- "is there a way to (auto|schedule|set up)"
32+
- "can you (check|monitor|watch|track).*for me"
33+
- "I keep (forgetting|missing|having to)"
34+
tags:
35+
- automation
36+
- scheduling
37+
- personal-assistant
38+
- productivity
39+
max_context_tokens: 1500
40+
---
41+
42+
# Routine Advisor
43+
44+
When the conversation suggests the user has a repeatable task or could benefit from automation, consider suggesting a routine.
45+
46+
## When to Suggest
47+
48+
Suggest a routine when you notice:
49+
- The user describes doing something repeatedly ("I check my PRs every morning")
50+
- The user mentions forgetting recurring tasks ("I keep forgetting to...")
51+
- The user asks you to do something that sounds periodic
52+
- You've learned enough about the user to propose a relevant automation
53+
- The user has installed extensions that enable new monitoring capabilities
54+
55+
## How to Suggest
56+
57+
Be specific and concrete. Not "Want me to set up a routine?" but rather: "I noticed you review PRs every morning. Want me to create a daily 9am routine that checks your open PRs and sends you a summary?"
58+
59+
Always include:
60+
1. What the routine would do (specific action)
61+
2. When it would run (specific schedule in plain language)
62+
3. How it would notify them (which channel they're on)
63+
64+
Wait for the user to confirm before creating.
65+
66+
## Pacing
67+
68+
- First 1-3 conversations: Do NOT suggest routines. Focus on helping and learning.
69+
- After learning 2-3 user patterns: Suggest your first routine. Keep it simple.
70+
- After 5+ conversations: Suggest more routines as patterns emerge.
71+
- Never suggest more than 1 routine per conversation unless the user is clearly interested.
72+
- If the user declines, wait at least 3 conversations before suggesting again.
73+
74+
## Creating Routines
75+
76+
Use the `routine_create` tool. Before creating, check `routine_list` to avoid duplicates.
77+
78+
Parameters:
79+
- `trigger_type`: Usually "cron" for scheduled tasks
80+
- `schedule`: Standard cron format. Common schedules:
81+
- Daily 9am: `0 9 * * *`
82+
- Weekday mornings: `0 9 * * MON-FRI`
83+
- Weekly Monday: `0 9 * * MON`
84+
- Every 2 hours during work: `0 9-17/2 * * MON-FRI`
85+
- Sunday evening: `0 18 * * SUN`
86+
- `action_type`: "lightweight" for simple checks, "full_job" for multi-step tasks
87+
- `prompt`: Clear, specific instruction for what the routine should do
88+
- `context_paths`: Workspace files to load as context (e.g., `["context/profile.json", "MEMORY.md"]`)
89+
90+
## Routine Ideas by User Type
91+
92+
**Developer:**
93+
- Daily PR review digest (check open PRs, summarize what needs attention)
94+
- CI/CD failure alerts (monitor build status)
95+
- Weekly dependency update check
96+
- Daily standup prep (summarize yesterday's work from daily logs)
97+
98+
**Professional:**
99+
- Morning briefing (today's priorities from memory + any pending tasks)
100+
- End-of-day summary (what was accomplished, what's pending)
101+
- Weekly goal review (check progress against stated goals)
102+
- Meeting prep reminders
103+
104+
**Health/Personal:**
105+
- Daily exercise or habit check-in
106+
- Weekly meal planning prompt
107+
- Monthly budget review reminder
108+
109+
**General:**
110+
- Daily news digest on topics of interest
111+
- Weekly reflection prompt (what went well, what to improve)
112+
- Periodic task/reminder check-in
113+
- Regular cleanup of stale tasks or notes
114+
- Weekly profile evolution (if the user has a profile in `context/profile.json`, suggest a Monday routine that reads the profile via `memory_read`, searches recent conversations for new patterns with `memory_search`, and updates the profile via `memory_write` if any fields should change with confidence > 0.6 — be conservative, only update with clear evidence)
115+
116+
## Awareness
117+
118+
Before suggesting, consider what tools and extensions are currently available. Only suggest routines the agent can actually execute. If a routine would need a tool that isn't installed, mention that too: "If you connect your calendar, I could also send you a morning briefing with today's meetings."

src/agent/agent_loop.rs

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ use crate::skills::SkillRegistry;
3131
use crate::tools::ToolRegistry;
3232
use crate::workspace::Workspace;
3333

34+
/// Static greeting persisted to DB and broadcast on first launch.
35+
///
36+
/// Sent before the LLM is involved so the user sees something immediately.
37+
/// The conversational onboarding (profile building, channel setup) happens
38+
/// organically in the subsequent turns driven by BOOTSTRAP.md.
39+
const BOOTSTRAP_GREETING: &str = include_str!("../workspace/seeds/GREETING.md");
40+
3441
/// Collapse a tool output string into a single-line preview for display.
3542
pub(crate) fn truncate_for_preview(output: &str, max_chars: usize) -> String {
3643
let collapsed: String = output
@@ -340,6 +347,32 @@ impl Agent {
340347

341348
/// Run the agent main loop.
342349
pub async fn run(self) -> Result<(), Error> {
350+
// Proactive bootstrap: persist the static greeting to DB *before*
351+
// starting channels so the first web client sees it via history.
352+
let bootstrap_thread_id = if self
353+
.workspace()
354+
.is_some_and(|ws| ws.take_bootstrap_pending())
355+
{
356+
tracing::debug!(
357+
"Fresh workspace detected — persisting static bootstrap greeting to DB"
358+
);
359+
if let Some(store) = self.store() {
360+
let thread_id = store
361+
.get_or_create_assistant_conversation("default", "gateway")
362+
.await
363+
.ok();
364+
if let Some(id) = thread_id {
365+
self.persist_assistant_response(id, "gateway", "default", BOOTSTRAP_GREETING)
366+
.await;
367+
}
368+
thread_id
369+
} else {
370+
None
371+
}
372+
} else {
373+
None
374+
};
375+
343376
// Start channels
344377
let mut message_stream = self.channels.start_all().await?;
345378

@@ -671,6 +704,30 @@ impl Agent {
671704
None
672705
};
673706

707+
// Bootstrap phase 2: register the thread in session manager and
708+
// broadcast the greeting via SSE for any clients already connected.
709+
// The greeting was already persisted to DB before start_all(), so
710+
// clients that connect after this point will see it via history.
711+
if let Some(id) = bootstrap_thread_id {
712+
// Use get_or_create_session (not resolve_thread) to avoid creating
713+
// an orphan thread. Then insert the DB-sourced thread directly.
714+
let session = self.session_manager.get_or_create_session("default").await;
715+
{
716+
use crate::agent::session::Thread;
717+
let mut sess = session.lock().await;
718+
let thread = Thread::with_id(id, sess.id);
719+
sess.active_thread = Some(id);
720+
sess.threads.entry(id).or_insert(thread);
721+
}
722+
self.session_manager
723+
.register_thread("default", "gateway", id, session)
724+
.await;
725+
726+
let mut out = OutgoingResponse::text(BOOTSTRAP_GREETING.to_string());
727+
out.thread_id = Some(id.to_string());
728+
let _ = self.channels.broadcast("gateway", "default", out).await;
729+
}
730+
674731
// Main message loop
675732
tracing::debug!("Agent {} ready and listening", self.config.name);
676733

@@ -864,9 +921,6 @@ impl Agent {
864921
}
865922

866923
async fn handle_message(&self, message: &IncomingMessage) -> Result<Option<String>, Error> {
867-
// Log at info level only for tracking without exposing PII (user_id can be a phone number)
868-
tracing::info!(message_id = %message.id, "Processing message");
869-
870924
// Log sensitive details at debug level for troubleshooting
871925
tracing::debug!(
872926
message_id = %message.id,
@@ -946,10 +1000,6 @@ impl Agent {
9461000
}
9471001

9481002
// Resolve session and thread
949-
tracing::debug!(
950-
message_id = %message.id,
951-
"Resolving session and thread"
952-
);
9531003
let (session, thread_id) = self
9541004
.session_manager
9551005
.resolve_thread(

0 commit comments

Comments
 (0)