Skip to content
Open
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
35 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
f0abb37
fix(safety): move prompt injection scanning into Workspace write/append
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
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
f29c541
fix: lazy-init sanitizer, check profile non-empty before skipping boo…
ilblackdragon Mar 15, 2026
a36f5c9
style: cargo fmt
ilblackdragon Mar 15, 2026
1aea7ca
Merge remote-tracking branch 'origin/staging' into jayzalowitz/chat-o…
ilblackdragon Mar 16, 2026
3071493
fix: duplicate language handler, empty LLM_BACKEND, test_rig style
ilblackdragon Mar 16, 2026
685f3d8
fix: update stale BOOTSTRAP.md write-protection comment [skip-regress…
ilblackdragon Mar 18, 2026
7dc877b
Merge remote-tracking branch 'origin/staging' into jayzalowitz/chat-o…
ilblackdragon Mar 18, 2026
8d678cf
fix: replace debug_assert panics with graceful error returns [skip-re…
ilblackdragon Mar 18, 2026
3af0518
Merge remote-tracking branch 'origin/staging' into jayzalowitz/chat-o…
ilblackdragon Mar 19, 2026
204bd85
fix: address Copilot review — schema label, env var check, path norma…
ilblackdragon Mar 19, 2026
ab321b6
style: cargo fmt
ilblackdragon Mar 19, 2026
4c2e0ee
Merge remote-tracking branch 'origin/staging' into jayzalowitz/chat-o…
ilblackdragon Mar 19, 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 @@ -31,7 +31,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 @@ -336,6 +343,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 +348 to +364
Copy link
Member Author

Choose a reason for hiding this comment

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

Acknowledged — the hardcoded "default" user_id is consistent with the current single-tenant model used throughout the codebase (agent loop, session manager, workspace). Deriving from ws.user_id() is a good future improvement when multi-tenant support is added, but changing it here would create an inconsistency with the rest of the bootstrap flow without the broader refactor. No code change.

} else {
None
}
} else {
None
};

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

Expand Down Expand Up @@ -659,6 +692,30 @@ impl Agent {
None
};

// 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;
Comment on lines +699 to +712
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed in 97ee5b6 — now uses get_or_create_session + Thread::with_id(id, sess.id) to insert the DB-sourced thread directly into the session, avoiding the orphan thread that resolve_thread(None) would create.


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 @@ -852,9 +909,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 @@ -934,10 +988,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
Loading
Loading