Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
e21383e
feat: port NPA psychographic profiling system into IronClaw
jayzalowitz Feb 23, 2026
0e7b77d
feat: replace chat-based onboarding with bootstrap greeting and works…
ilblackdragon Mar 11, 2026
9e59af8
Merge remote-tracking branch 'origin/staging' into jayzalowitz/chat-o…
ilblackdragon Mar 12, 2026
67e1aad
feat(safety): sanitize identity file writes via Sanitizer to prevent …
ilblackdragon Mar 12, 2026
7cb9f86
docs: update profile_onboarding_completed comment to reflect current …
ilblackdragon Mar 12, 2026
104e405
fix(setup): use env_or_override for NEARAI_API_KEY in model fetch config
ilblackdragon Mar 12, 2026
3956833
fix(agent): correct channel/user_id in bootstrap greeting persist call
ilblackdragon Mar 12, 2026
63c6637
fix(web): remove all inline event handlers for CSP compliance
ilblackdragon Mar 12, 2026
71c0aeb
fix(agent): align bootstrap message user/channel and update fixture s…
ilblackdragon Mar 12, 2026
5fa0433
style: cargo fmt
ilblackdragon Mar 12, 2026
fd3e8b6
fix(safety): address PR review — expand injection scanning and harden…
ilblackdragon Mar 13, 2026
ecbfd20
Merge remote-tracking branch 'origin/staging' into jayzalowitz/chat-o…
ilblackdragon Mar 13, 2026
3d38f6d
style: cargo fmt
ilblackdragon Mar 13, 2026
f14d136
fix(setup): detect env-provided LLM keys during quick-mode onboarding
ilblackdragon Mar 13, 2026
0c166a3
Merge remote-tracking branch 'origin/staging' into jayzalowitz/chat-o…
ilblackdragon Mar 13, 2026
aa6605f
fix(test): update routine_create_list to expect 7-field normalized cron
ilblackdragon Mar 13, 2026
692ade6
feat(setup): skip LLM provider prompts when NEARAI_API_KEY is present
ilblackdragon Mar 15, 2026
d753bb6
fix: unify default model, static bootstrap greeting, and web UI cleanup
ilblackdragon Mar 15, 2026
004d0ef
feat(gateway): full settings page polish with all tiers
ilblackdragon Mar 14, 2026
452da4b
feat(gateway): polish settings page and remove registered tools debug…
ilblackdragon Mar 15, 2026
a7aeff4
fix(gateway): address PR review feedback [skip-regression-check]
ilblackdragon Mar 15, 2026
f8ffd9e
fix(e2e): update tests for unified settings tab layout [skip-regressi…
ilblackdragon Mar 15, 2026
f0abb37
fix(safety): move prompt injection scanning into Workspace write/append
ilblackdragon Mar 15, 2026
93cd14b
fix(gateway): address second round of PR review feedback [skip-regres…
ilblackdragon Mar 15, 2026
e110159
fix(e2e): fix WASM channel card selector and skills remove confirm [s…
ilblackdragon Mar 15, 2026
d63883a
fix(gateway): address third round of PR review feedback [skip-regress…
ilblackdragon Mar 15, 2026
706b852
fix(e2e): fix auth_completed reload test race condition [skip-regress…
ilblackdragon Mar 15, 2026
4fc0a3c
fix(e2e): debug auth_completed reload test with function counter [ski…
ilblackdragon Mar 15, 2026
c636f85
Merge remote-tracking branch 'origin/staging' into jayzalowitz/chat-o…
ilblackdragon Mar 15, 2026
10e35a7
style: cargo fmt
ilblackdragon Mar 15, 2026
9885f7e
feat(gateway): localize all settings labels, descriptions, and channe…
ilblackdragon Mar 15, 2026
97ee5b6
fix: address Copilot review — merge marker order, orphan thread, stal…
ilblackdragon Mar 15, 2026
c4fb967
style: cargo fmt
ilblackdragon Mar 15, 2026
4d670ea
style: fmt agent_loop.rs (CI stable rustfmt)
ilblackdragon Mar 15, 2026
a657ddd
fix(gateway): localize remaining hardcoded UI strings [skip-regressio…
ilblackdragon Mar 15, 2026
c59ae34
fix(gateway): confirm modal a11y, Esc/click-outside, search guard [sk…
ilblackdragon Mar 15, 2026
f29c541
fix: lazy-init sanitizer, check profile non-empty before skipping boo…
ilblackdragon Mar 15, 2026
a36f5c9
style: cargo fmt
ilblackdragon Mar 15, 2026
161b754
Merge remote-tracking branch 'origin/jayzalowitz/chat-onboarding-and-…
ilblackdragon Mar 16, 2026
5722bc3
Merge remote-tracking branch 'origin/settings-page-polish' into merge…
ilblackdragon Mar 16, 2026
1490841
feat: UX overhaul — shared design system, polished CLI/web experience
ilblackdragon Mar 16, 2026
e447de7
feat(ux): complete UX overhaul — design system, onboarding, web polish
ilblackdragon Mar 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ DATABASE_POOL_SIZE=10
# Base URL defaults to https://private.near.ai
# 2. API key: Set NEARAI_API_KEY to use API key auth from cloud.near.ai.
# Base URL defaults to https://cloud-api.near.ai
NEARAI_MODEL=zai-org/GLM-5-FP8
NEARAI_MODEL=Qwen/Qwen3.5-122B-A10B
NEARAI_BASE_URL=https://private.near.ai
NEARAI_AUTH_URL=https://private.near.ai
# NEARAI_SESSION_TOKEN=sess_... # hosting providers: set this
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ src/
├── secrets/ # Secrets management (AES-256-GCM, OS keychain for master key)
├── profile.rs # Psychographic profile types, 9-dimension analysis framework
├── setup/ # 7-step onboarding wizard — see src/setup/README.md
├── skills/ # SKILL.md prompt extension system — see .claude/rules/skills.md
Expand Down
75 changes: 75 additions & 0 deletions skills/delegation/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
name: delegation
version: 0.1.0
description: Helps users delegate tasks, break them into steps, set deadlines, and track progress via routines and memory.
activation:
keywords:
- delegate
- hand off
- assign task
- help me with
- take care of
- remind me to
- schedule
- plan my
- manage my
- track this
patterns:
- "can you.*handle"
- "I need (help|someone) to"
- "take over"
- "set up a reminder"
- "follow up on"
tags:
- personal-assistant
- task-management
- delegation
max_context_tokens: 1500
---

# Task Delegation Assistant

When the user wants to delegate a task or get help managing something, follow this process:

## 1. Clarify the Task

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.

## 2. Break It Down

Decompose the task into concrete, actionable steps. Use `memory_write` to persist the task plan to a path like `tasks/{task-name}.md` with:
- Clear description
- Steps with checkboxes
- Due date (if any)
- Status: pending/in-progress/done

## 3. Set Up Tracking

If the task is recurring or has a deadline:
- Create a routine using `routine_create` for scheduled check-ins
- Add a heartbeat item if it needs daily monitoring
- Set up an event-triggered routine if it depends on external input

## 4. Use Profile Context

Check `USER.md` for the user's preferences:
- **Proactivity level**: High = check in frequently. Low = only report on completion.
- **Communication style**: Match their preferred tone and detail level.
- **Focus areas**: Prioritize tasks that align with their stated goals.

## 5. Execute or Queue

- If you can do it now (search, draft, organize, calculate), do it immediately.
- If it requires waiting, external action, or follow-up, create a reminder routine.
- If it requires tools you don't have, explain what's needed and suggest alternatives.

## 6. Report Back

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.

## Communication Guidelines

- Be direct and action-oriented
- Confirm understanding before acting on ambiguous requests
- When in doubt about autonomy level, ask once then remember the answer
- Use `memory_write` to track delegation preferences for future reference
118 changes: 118 additions & 0 deletions skills/routine-advisor/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
name: routine-advisor
version: 0.1.0
description: Suggests relevant cron routines based on user context, goals, and observed patterns
activation:
keywords:
- every day
- every morning
- every week
- routine
- automate
- remind me
- check daily
- monitor
- recurring
- schedule
- habit
- workflow
- keep forgetting
- always have to
- repetitive
- notifications
- digest
- summary
- review daily
- weekly review
patterns:
- "I (always|usually|often|regularly) (check|do|look at|review)"
- "every (morning|evening|week|day|monday|friday)"
- "I (wish|want) (I|it) (could|would) (automatically|auto)"
- "is there a way to (auto|schedule|set up)"
- "can you (check|monitor|watch|track).*for me"
- "I keep (forgetting|missing|having to)"
tags:
- automation
- scheduling
- personal-assistant
- productivity
max_context_tokens: 1500
---

# Routine Advisor

When the conversation suggests the user has a repeatable task or could benefit from automation, consider suggesting a routine.

## When to Suggest

Suggest a routine when you notice:
- The user describes doing something repeatedly ("I check my PRs every morning")
- The user mentions forgetting recurring tasks ("I keep forgetting to...")
- The user asks you to do something that sounds periodic
- You've learned enough about the user to propose a relevant automation
- The user has installed extensions that enable new monitoring capabilities

## How to Suggest

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?"

Always include:
1. What the routine would do (specific action)
2. When it would run (specific schedule in plain language)
3. How it would notify them (which channel they're on)

Wait for the user to confirm before creating.

## Pacing

- First 1-3 conversations: Do NOT suggest routines. Focus on helping and learning.
- After learning 2-3 user patterns: Suggest your first routine. Keep it simple.
- After 5+ conversations: Suggest more routines as patterns emerge.
- Never suggest more than 1 routine per conversation unless the user is clearly interested.
- If the user declines, wait at least 3 conversations before suggesting again.

## Creating Routines

Use the `routine_create` tool. Before creating, check `routine_list` to avoid duplicates.

Parameters:
- `trigger_type`: Usually "cron" for scheduled tasks
- `schedule`: Standard cron format. Common schedules:
- Daily 9am: `0 9 * * *`
- Weekday mornings: `0 9 * * MON-FRI`
- Weekly Monday: `0 9 * * MON`
- Every 2 hours during work: `0 9-17/2 * * MON-FRI`
- Sunday evening: `0 18 * * SUN`
- `action_type`: "lightweight" for simple checks, "full_job" for multi-step tasks
- `prompt`: Clear, specific instruction for what the routine should do
- `context_paths`: Workspace files to load as context (e.g., `["context/profile.json", "MEMORY.md"]`)

## Routine Ideas by User Type

**Developer:**
- Daily PR review digest (check open PRs, summarize what needs attention)
- CI/CD failure alerts (monitor build status)
- Weekly dependency update check
- Daily standup prep (summarize yesterday's work from daily logs)

**Professional:**
- Morning briefing (today's priorities from memory + any pending tasks)
- End-of-day summary (what was accomplished, what's pending)
- Weekly goal review (check progress against stated goals)
- Meeting prep reminders

**Health/Personal:**
- Daily exercise or habit check-in
- Weekly meal planning prompt
- Monthly budget review reminder

**General:**
- Daily news digest on topics of interest
- Weekly reflection prompt (what went well, what to improve)
- Periodic task/reminder check-in
- Regular cleanup of stale tasks or notes
- 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)

## Awareness

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."
64 changes: 57 additions & 7 deletions src/agent/agent_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ use crate::skills::SkillRegistry;
use crate::tools::ToolRegistry;
use crate::workspace::Workspace;

/// Static greeting persisted to DB and broadcast on first launch.
///
/// Sent before the LLM is involved so the user sees something immediately.
/// The conversational onboarding (profile building, channel setup) happens
/// organically in the subsequent turns driven by BOOTSTRAP.md.
const BOOTSTRAP_GREETING: &str = include_str!("../workspace/seeds/GREETING.md");

/// Collapse a tool output string into a single-line preview for display.
pub(crate) fn truncate_for_preview(output: &str, max_chars: usize) -> String {
let collapsed: String = output
Expand Down Expand Up @@ -253,6 +260,32 @@ impl Agent {

/// Run the agent main loop.
pub async fn run(self) -> Result<(), Error> {
// Proactive bootstrap: persist the static greeting to DB *before*
// starting channels so the first web client sees it via history.
let bootstrap_thread_id = if self
.workspace()
.is_some_and(|ws| ws.take_bootstrap_pending())
{
tracing::debug!(
"Fresh workspace detected — persisting static bootstrap greeting to DB"
);
if let Some(store) = self.store() {
let thread_id = store
.get_or_create_assistant_conversation("default", "gateway")
.await
.ok();
if let Some(id) = thread_id {
self.persist_assistant_response(id, "gateway", "default", BOOTSTRAP_GREETING)
.await;
}
thread_id
Comment on lines +273 to +281
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The errors from get_or_create_assistant_conversation and persist_assistant_response are being ignored. This can lead to silent failures where the bootstrap greeting is not persisted to the database, which could be confusing for new users. It would be more robust to handle these potential errors, for example by logging them. This aligns with the principle of robust error handling and logging, as seen in the rule regarding tokio::task::spawn_blocking.

                let thread_id = match store
                    .get_or_create_assistant_conversation("default", "gateway")
                    .await
                {
                    Ok(id) => {
                        if let Err(e) = self
                            .persist_assistant_response(id, "gateway", "default", BOOTSTRAP_GREETING)
                            .await
                        {
                            tracing::warn!("Failed to persist bootstrap greeting: {}", e);
                        }
                        Some(id)
                    }
                    Err(e) => {
                        tracing::warn!("Failed to get or create bootstrap conversation: {}", e);
                        None
                    }
                };
References
  1. When handling errors, log them to capture debugging information and prevent silent failures. It is good practice to distinguish between different error types in the message.

} else {
None
}
} else {
None
};

// Start channels
let mut message_stream = self.channels.start_all().await?;

Expand Down Expand Up @@ -537,6 +570,30 @@ impl Agent {
// Extract engine ref for use in message loop
let routine_engine_for_loop = routine_handle.as_ref().map(|(_, e)| Arc::clone(e));

// Bootstrap phase 2: register the thread in session manager and
// broadcast the greeting via SSE for any clients already connected.
// The greeting was already persisted to DB before start_all(), so
// clients that connect after this point will see it via history.
if let Some(id) = bootstrap_thread_id {
// Use get_or_create_session (not resolve_thread) to avoid creating
// an orphan thread. Then insert the DB-sourced thread directly.
let session = self.session_manager.get_or_create_session("default").await;
{
use crate::agent::session::Thread;
let mut sess = session.lock().await;
let thread = Thread::with_id(id, sess.id);
sess.active_thread = Some(id);
sess.threads.entry(id).or_insert(thread);
}
self.session_manager
.register_thread("default", "gateway", id, session)
.await;

let mut out = OutgoingResponse::text(BOOTSTRAP_GREETING.to_string());
out.thread_id = Some(id.to_string());
let _ = self.channels.broadcast("gateway", "default", out).await;
}

// Main message loop
tracing::debug!("Agent {} ready and listening", self.config.name);

Expand Down Expand Up @@ -738,9 +795,6 @@ impl Agent {
}

async fn handle_message(&self, message: &IncomingMessage) -> Result<Option<String>, Error> {
// Log at info level only for tracking without exposing PII (user_id can be a phone number)
tracing::info!(message_id = %message.id, "Processing message");

// Log sensitive details at debug level for troubleshooting
tracing::debug!(
message_id = %message.id,
Expand Down Expand Up @@ -823,10 +877,6 @@ impl Agent {
}

// Resolve session and thread
tracing::debug!(
message_id = %message.id,
"Resolving session and thread"
);
let (session, thread_id) = self
.session_manager
.resolve_thread(
Expand Down
28 changes: 26 additions & 2 deletions src/agent/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ impl<'a> LoopDelegate for ChatDelegate<'a> {
.channels
.send_status(
&self.message.channel,
StatusUpdate::Thinking("Calling LLM...".into()),
StatusUpdate::Thinking(format!("Thinking (step {iteration})...")),
&self.message.metadata,
)
.await;
Expand Down Expand Up @@ -440,7 +440,7 @@ impl<'a> LoopDelegate for ChatDelegate<'a> {
.channels
.send_status(
&self.message.channel,
StatusUpdate::Thinking(format!("Executing {} tool(s)...", tool_calls.len())),
StatusUpdate::Thinking(contextual_tool_message(&tool_calls)),
&self.message.metadata,
)
.await;
Expand Down Expand Up @@ -972,6 +972,30 @@ pub(super) fn check_auth_required(
Some((name, instructions))
}

/// Build a contextual thinking message based on tool names.
///
/// Instead of a generic "Executing 2 tool(s)..." this returns messages like
/// "Running command..." or "Fetching page..." for single-tool calls, falling
/// back to "Executing N tool(s)..." for multi-tool calls.
fn contextual_tool_message(tool_calls: &[crate::llm::ToolCall]) -> String {
if tool_calls.len() == 1 {
match tool_calls[0].name.as_str() {
"shell" => "Running command...".into(),
"web_fetch" => "Fetching page...".into(),
"memory_search" => "Searching memory...".into(),
"memory_write" => "Writing to memory...".into(),
"memory_read" => "Reading memory...".into(),
"http_request" => "Making HTTP request...".into(),
"file_read" => "Reading file...".into(),
"file_write" => "Writing file...".into(),
"json_transform" => "Transforming data...".into(),
name => format!("Running {name}..."),
}
} else {
format!("Executing {} tool(s)...", tool_calls.len())
}
}

/// Compact messages for retry after a context-length-exceeded error.
///
/// Keeps all `System` messages (which carry the system prompt and instructions),
Expand Down
Loading
Loading