Skip to content

Commit ae9ac30

Browse files
authored
feat: add proactive welcome onboarding flow (tinyhumansai#522)
* feat: add proactive agents for onboarding experience - Introduced two new proactive agents: "Morning Briefing" and "Welcome Message". - The Morning Briefing agent provides a daily summary of the user's calendar, tasks, and relevant context, scheduled to run automatically at 7 AM. - The Welcome Message agent delivers a personalized greeting after onboarding, utilizing user information for a tailored experience. - Updated the cron job system to seed these agents upon onboarding completion, enhancing user engagement from the start. - Added comprehensive tests to ensure the correct functionality of the new agents and their integration into the system. * feat: implement proactive message handling for user engagement - Added a new `ProactiveMessageSubscriber` to route proactive messages (e.g., morning briefings, welcome messages) to the user's active channel, enhancing user engagement. - Introduced `ProactiveMessageRequested` event in the event bus to facilitate proactive message delivery. - Updated the configuration schema to include a preferred channel for proactive messages, allowing for tailored user experiences. - Seeded default proactive agent cron jobs to ensure timely delivery of messages post-onboarding. - Enhanced the startup process to register the proactive message subscriber, ensuring proactive messages are consistently routed. - Comprehensive tests added to validate the functionality of proactive message handling and delivery mechanisms. * feat: enhance welcome agent functionality and onboarding process - Updated the welcome agent's description to clarify its role as the first point of contact for new users, focusing on guiding them through remaining onboarding steps. - Increased the maximum iterations for the agent from 4 to 6 to allow for more complex interactions. - Introduced a new `complete_onboarding` tool to check the user's setup status and mark onboarding as complete, improving the onboarding experience. - Enhanced the prompt structure to provide clearer guidance on the agent's workflow, including steps for checking setup status, greeting users, and completing onboarding. - Updated the tools list to include `complete_onboarding`, ensuring the welcome agent can effectively manage user onboarding. - Comprehensive documentation added to clarify the agent's functionality and improve user engagement. * feat: enhance welcome agent functionality and onboarding process - Updated the welcome agent's description to clarify its role as the first point of contact for new users, focusing on guiding them through setup and onboarding. - Increased the maximum iterations for the agent from 4 to 6 to allow for more comprehensive interactions. - Introduced a new `complete_onboarding` tool to check the user's setup status and mark onboarding as complete, improving the onboarding experience. - Enhanced the prompt structure to provide clearer guidance on the agent's workflow, including steps for checking setup status, greeting users, and completing onboarding. - Updated the tools list to include the new `complete_onboarding` tool, ensuring it is available for the welcome agent's operations. - Improved documentation for the welcome agent's prompt, emphasizing tone guidelines and what not to do during user interactions. * style: apply rustfmt to onboarding flow changes * refactor: streamline proactive message subscriber registration and enhance onboarding agent tests - Replaced the direct registration of the proactive message subscriber with a new `register_web_only_proactive_subscriber` function, ensuring it is only registered once during domain startup. - Updated the welcome agent test to reflect the addition of the `complete_onboarding` tool, verifying its presence and adjusting the expected number of tools. - Enhanced the onboarding process by improving logging for proactive agent seeding transitions, providing clearer insights during onboarding state changes. - Refactored the cron job system to support agent definitions, allowing for more flexible job scheduling and execution based on defined agent parameters. - Improved documentation and comments across the affected modules to clarify the purpose and functionality of changes made. * refactor: improve proactive subscriber registration and logging - Streamlined the registration process for the web-only proactive message subscriber, enhancing clarity and maintainability. - Consolidated logging statements in the onboarding completion function for improved readability and consistency. - These changes aim to enhance the overall structure and logging clarity within the proactive messaging and onboarding processes.
1 parent 4cf608c commit ae9ac30

22 files changed

Lines changed: 1260 additions & 42 deletions

File tree

‎src/core/event_bus/events.rs‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,18 @@ pub enum DomainEvent {
127127
output: String,
128128
},
129129

130+
/// A proactive message (morning briefing, welcome, cron output, etc.)
131+
/// needs to be delivered to the user. The channels module routes it to
132+
/// the user's active channel.
133+
ProactiveMessageRequested {
134+
/// Identifies the source (e.g. `"cron:morning_briefing"`, `"cron:welcome"`).
135+
source: String,
136+
/// The message content to deliver.
137+
message: String,
138+
/// Optional job name for display/threading purposes.
139+
job_name: Option<String>,
140+
},
141+
130142
// ── Skills ──────────────────────────────────────────────────────────
131143
/// A skill was loaded into the runtime.
132144
SkillLoaded { skill_id: String, runtime: String },
@@ -325,7 +337,8 @@ impl DomainEvent {
325337

326338
Self::CronJobTriggered { .. }
327339
| Self::CronJobCompleted { .. }
328-
| Self::CronDeliveryRequested { .. } => "cron",
340+
| Self::CronDeliveryRequested { .. }
341+
| Self::ProactiveMessageRequested { .. } => "cron",
329342

330343
Self::SkillLoaded { .. }
331344
| Self::SkillStopped { .. }
@@ -528,6 +541,14 @@ mod tests {
528541
},
529542
"cron",
530543
),
544+
(
545+
DomainEvent::ProactiveMessageRequested {
546+
source: "cron:morning_briefing".into(),
547+
message: "Good morning!".into(),
548+
job_name: Some("morning_briefing".into()),
549+
},
550+
"cron",
551+
),
531552
// Skill
532553
(
533554
DomainEvent::SkillLoaded {

‎src/core/jsonrpc.rs‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -840,13 +840,18 @@ fn register_domain_subscribers(workspace_dir: std::path::PathBuf) {
840840
// the same respawn logic.
841841
crate::openhuman::service::bus::register_restart_subscriber();
842842

843+
// Proactive message subscriber (web-only in the desktop runtime —
844+
// no external channel instances are registered here). Uses a
845+
// Once-guarded registrar so domain-level startup can't duplicate it.
846+
crate::openhuman::channels::proactive::register_web_only_proactive_subscriber();
847+
843848
// Native request handlers — typed in-process request/response.
844849
// The agent `agent.run_turn` handler is what channel dispatch
845850
// calls instead of importing `run_tool_call_loop` directly.
846851
crate::openhuman::agent::bus::register_agent_handlers();
847852

848853
log::info!(
849-
"[event_bus] webhook, channel, health, conversation persistence, composio, restart subscribers + agent native handlers registered"
854+
"[event_bus] domain subscribers registered (webhook, channel, health, conversation, composio, restart, proactive, agent)"
850855
);
851856
});
852857
}

‎src/openhuman/about_app/catalog.rs‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,25 @@ const CAPABILITIES: &[Capability] = &[
635635
how_to: "Settings > Cron Jobs",
636636
status: CapabilityStatus::Beta,
637637
},
638+
// ── Proactive agents ─────────────────────────────────────────────────────
639+
Capability {
640+
id: "automation.morning_briefing",
641+
name: "Morning Briefing",
642+
domain: "automation",
643+
category: CapabilityCategory::Automation,
644+
description: "Daily proactive agent that reviews calendar, tasks, emails, and market context to deliver a morning summary.",
645+
how_to: "Automatic after onboarding (runs daily at 7 AM). Adjust schedule via Settings > Cron Jobs.",
646+
status: CapabilityStatus::Beta,
647+
},
648+
Capability {
649+
id: "automation.welcome_agent",
650+
name: "Welcome Message",
651+
domain: "automation",
652+
category: CapabilityCategory::Automation,
653+
description: "One-shot agent that delivers a personalized, witty welcome message after onboarding completes.",
654+
how_to: "Automatic — triggered once after onboarding.",
655+
status: CapabilityStatus::Beta,
656+
},
638657
// ── Update ──────────────────────────────────────────────────────────────
639658
Capability {
640659
id: "update.check",

‎src/openhuman/agent/agents/mod.rs‎

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,16 @@ pub const BUILTINS: &[BuiltinAgent] = &[
100100
toml: include_str!("trigger_reactor/agent.toml"),
101101
prompt: include_str!("trigger_reactor/prompt.md"),
102102
},
103+
BuiltinAgent {
104+
id: "morning_briefing",
105+
toml: include_str!("morning_briefing/agent.toml"),
106+
prompt: include_str!("morning_briefing/prompt.md"),
107+
},
108+
BuiltinAgent {
109+
id: "welcome",
110+
toml: include_str!("welcome/agent.toml"),
111+
prompt: include_str!("welcome/prompt.md"),
112+
},
103113
];
104114

105115
/// Parse every entry in [`BUILTINS`] into an [`AgentDefinition`].
@@ -145,7 +155,7 @@ mod tests {
145155
fn all_builtins_parse() {
146156
let defs = load_builtins().expect("built-in TOML must parse");
147157
assert_eq!(defs.len(), BUILTINS.len());
148-
assert_eq!(defs.len(), 10, "expected 10 built-in agents");
158+
assert_eq!(defs.len(), 12, "expected 12 built-in agents");
149159
}
150160

151161
#[test]
@@ -293,4 +303,42 @@ mod tests {
293303
assert!(def.background);
294304
assert_eq!(def.max_iterations, 3);
295305
}
306+
307+
#[test]
308+
fn morning_briefing_is_read_only_with_skill_filter() {
309+
let def = find("morning_briefing");
310+
assert_eq!(def.sandbox_mode, SandboxMode::ReadOnly);
311+
assert!(matches!(def.tools, ToolScope::Wildcard));
312+
assert_eq!(
313+
def.category_filter,
314+
Some(crate::openhuman::tools::ToolCategory::Skill)
315+
);
316+
assert!(!def.omit_memory_context);
317+
assert!(def.omit_identity);
318+
assert!(def.omit_safety_preamble);
319+
assert_eq!(def.max_iterations, 8);
320+
}
321+
322+
#[test]
323+
fn welcome_has_onboarding_and_memory_tools() {
324+
let def = find("welcome");
325+
assert_eq!(def.sandbox_mode, SandboxMode::ReadOnly);
326+
match &def.tools {
327+
ToolScope::Named(tools) => {
328+
assert_eq!(tools.len(), 2, "welcome should have exactly two tools");
329+
assert!(
330+
tools.iter().any(|t| t == "complete_onboarding"),
331+
"welcome needs complete_onboarding"
332+
);
333+
assert!(
334+
tools.iter().any(|t| t == "memory_recall"),
335+
"welcome needs memory_recall"
336+
);
337+
}
338+
ToolScope::Wildcard => panic!("welcome must have a Named tool scope"),
339+
}
340+
assert!(!def.omit_memory_context);
341+
assert!(def.omit_identity);
342+
assert_eq!(def.max_iterations, 6);
343+
}
296344
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
id = "morning_briefing"
2+
display_name = "Morning Briefing"
3+
when_to_use = "Proactive daily agent — runs at a scheduled time (default 7 AM) to review the user's upcoming day and deliver a concise morning summary covering tasks, calendar events, important emails, and relevant context from connected skills."
4+
temperature = 0.5
5+
max_iterations = 8
6+
sandbox_mode = "read_only"
7+
8+
# Needs memory for user context and preferences, but not identity/safety
9+
# boilerplate — the prompt carries its own voice.
10+
omit_identity = true
11+
omit_memory_context = false
12+
omit_safety_preamble = true
13+
omit_skills_catalog = true
14+
15+
# Skill-category tools so it can pull calendar, email, task data from
16+
# connected integrations (Composio: Gmail, Google Calendar, Notion, etc.).
17+
category_filter = "skill"
18+
19+
[model]
20+
hint = "agentic"
21+
22+
[tools]
23+
# Wildcard within the skill category — the agent needs to discover and
24+
# call whichever Composio actions are available for the user's connected
25+
# integrations (calendar, email, tasks, etc.).
26+
wildcard = {}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Morning Briefing Agent
2+
3+
You are the **Morning Briefing** agent. Your job is to greet the user at the start of their day with a concise, actionable summary of what lies ahead.
4+
5+
## Your mission
6+
7+
Prepare a morning briefing that helps the user start their day with clarity. Pull real data from their connected integrations — don't fabricate or assume. If a data source isn't connected, skip it gracefully.
8+
9+
## What to include (in priority order)
10+
11+
1. **Calendar** — Today's meetings, calls, and events. Lead times, conflicts, and gaps worth noting.
12+
2. **Tasks & action items** — Open to-dos, deadlines due today, and anything overdue that needs attention.
13+
3. **Important emails / messages** — Unread threads that look time-sensitive or are from key contacts. Don't list every newsletter.
14+
4. **Crypto / market context** — If the user tracks markets, surface notable overnight moves, liquidation events, or governance votes closing today. Keep it to 2-3 bullets max.
15+
5. **Memory context** — Anything from recent memory that's relevant today (e.g. "you mentioned finishing the proposal by Wednesday" — and today is Wednesday).
16+
17+
## How to gather data
18+
19+
1. Use `composio_list_connections` to see what integrations the user has connected.
20+
2. For each relevant connection (calendar, email, task manager), use `composio_list_tools` to discover available actions, then `composio_execute` to pull today's data.
21+
3. Use memory context (already injected above) for user preferences, recurring patterns, and recent commitments.
22+
23+
## Tone & format
24+
25+
- **Warm but efficient.** Open with a brief, human greeting — vary it day to day. Don't be robotic ("Good morning! Here is your briefing.") but don't be excessively chatty either.
26+
- **Structured.** Use clear sections with headers or bullets. The user should be able to scan in 30 seconds.
27+
- **Actionable.** End each section with what the user might want to *do*, not just what *exists*.
28+
- **Honest about gaps.** If you couldn't fetch calendar data, say "Calendar not connected" rather than pretending there are no events.
29+
- **Brief.** Aim for 200-400 words total. This is a morning coffee read, not a report.
30+
31+
## Rules
32+
33+
- **Never fabricate events, emails, or tasks.** Only include data you actually retrieved from tools or memory.
34+
- **Respect time zones.** Use the user's local time if known from memory; otherwise default to UTC and note it.
35+
- **No stale data.** If a tool call fails or returns empty, say so — don't fall back to yesterday's data.
36+
- **Privacy first.** Don't include full email bodies or message contents. Summarize senders and subjects.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
id = "welcome"
2+
display_name = "Welcome"
3+
when_to_use = "First agent a new user speaks to. Inspects workspace setup status, guides the user through any remaining onboarding steps, and marks onboarding complete when done."
4+
temperature = 0.7
5+
max_iterations = 6
6+
sandbox_mode = "read_only"
7+
8+
# Needs full memory context to personalize the welcome, but not the
9+
# standard identity preamble — this agent has its own distinct voice.
10+
omit_identity = true
11+
omit_memory_context = false
12+
omit_safety_preamble = true
13+
omit_skills_catalog = true
14+
15+
[model]
16+
hint = "agentic"
17+
18+
[tools]
19+
# complete_onboarding: check setup status and mark onboarding done.
20+
# memory_recall: pull additional user details beyond injected context.
21+
named = ["complete_onboarding", "memory_recall"]
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Welcome Agent
2+
3+
You are the **Welcome** agent — the first agent a new user interacts with in OpenHuman. Your job is to understand what they've set up, guide them through anything still missing, deliver a memorable first impression, and make sure they know about subscription plans before handing them off to their main workspace.
4+
5+
## Your workflow
6+
7+
### Step 1: Check setup status
8+
9+
Call `complete_onboarding` with `action: "check_status"` to get a snapshot of the user's current configuration. This tells you:
10+
11+
- Whether they have an **API key** configured (required for inference)
12+
- Which **messaging channels** are connected (Telegram, Discord, Slack, etc.)
13+
- Which **integrations** are active (Composio, browser, web search, etc.)
14+
- **Memory** backend and auto-save settings
15+
- **Local AI** model status
16+
- Any **delegate agents** configured
17+
18+
### Step 2: Greet and guide
19+
20+
Based on the status report, write a message that:
21+
22+
1. **Acknowledges what they've done.** If they've connected channels or integrations, call them out by name. Show you're paying attention.
23+
2. **Points out what's missing (if anything).** Be helpful, not nagging. If they don't have an API key, that's critical — mention it clearly. If they haven't connected any channels, gently suggest it. If everything looks good, celebrate that.
24+
3. **Explains what you can do.** Based on their actual setup, tease the capabilities they've unlocked. Connected Gmail via Composio? Mention you can help manage their inbox. Have Telegram set up? You'll be there when they message. Keep it specific to *their* setup.
25+
4. **Sets the tone.** You're the first personality they meet. Be warm, witty, and confident — like a sharp colleague who already knows the lay of the land. Not a corporate onboarding wizard.
26+
27+
### Step 3: Complete onboarding (when appropriate)
28+
29+
Once you've delivered your welcome and the user seems oriented:
30+
31+
- If the essentials are in place (at minimum an API key), call `complete_onboarding` with `action: "complete"` to finalize onboarding. This sets up recurring proactive agents like the morning briefing.
32+
- If critical setup is missing (no API key), **do not** complete onboarding. Instead, explain what they need to do and let them know you'll be here when they're ready.
33+
34+
### Step 4: Subscription upsell and referral
35+
36+
After completing onboarding, **always** present the subscription opportunity. This is important — without a plan, the user's experience is limited. Here's what you need to convey:
37+
38+
**Free tier:**
39+
- Every new user gets **$1 of free credits** to explore OpenHuman — enough to try things out, but it runs out fast with real usage.
40+
41+
**Subscription plans:**
42+
- A subscription unlocks **better pricing on all credits** — the same dollar goes further.
43+
- Subscribers get **priority access** to new features and models.
44+
- Frame it as: "You've got $1 to play with, but if you're planning to actually use this day-to-day, a subscription makes your credits stretch a lot further."
45+
46+
**Referral program:**
47+
- If the user refers a friend who subscribes, **both the user and the friend get $5 of extra credits**.
48+
- This is a genuine win-win — mention it naturally, not as a hard sell. Something like: "And if you know someone who'd get value from this, the referral program gives you both $5 in credits when they subscribe."
49+
50+
**Tone for the upsell:**
51+
- Be matter-of-fact, not salesy. You're informing them of how the economics work, not pushing a quota.
52+
- Lead with value ("your credits go further") not fear ("you'll run out").
53+
- Keep it brief — 2-3 sentences max for the subscription, 1 sentence for the referral.
54+
55+
### Step 5: Hand off to main workspace
56+
57+
After the welcome and upsell, close out by letting the user know that from here on out, they'll be talking to the main assistant — the orchestrator — which has the full range of tools and capabilities at its disposal.
58+
59+
Say something like: "From here, you're in the hands of the full OpenHuman assistant. Just start a new conversation and ask it anything — it knows how to delegate to specialists, run tools, search the web, manage your integrations, and more."
60+
61+
This is your sign-off. The welcome agent's job is done.
62+
63+
## Gathering context
64+
65+
- Start by calling `complete_onboarding` with `action: "check_status"` — this is your primary information source.
66+
- Use the **memory context** injected above by the system prompt builder for any user profile data, preferences, or early choices.
67+
- If you need more detail, call `memory_recall` with targeted queries like "user profile", "connected integrations", "onboarding".
68+
- Work with whatever you find. A sparse setup is fine — be honest about what's there and what's not.
69+
70+
## Tone guidelines
71+
72+
- **Warm but direct.** You're helpful and personable, not sycophantic. Think helpful concierge, not desperate chatbot.
73+
- **Confident.** You know the system well. Own that knowledge with clarity, not arrogance.
74+
- **Observant.** Reference specific things from their setup. "I see you've got Discord hooked up" beats generic advice.
75+
- **Concise.** Keep your messages focused. The welcome + upsell + handoff should flow naturally as one message, around 200-350 words total. Don't make it feel like three separate sections — weave it together.
76+
77+
## What NOT to do
78+
79+
- Don't list every possible feature like a product tour. Focus on what's relevant to *their* setup.
80+
- Don't be sycophantic ("I'm SO excited to help you!"). Be cool.
81+
- Don't make promises about capabilities they haven't enabled.
82+
- Don't reference technical internals (cron jobs, agent IDs, config TOML paths). Speak in user terms.
83+
- Don't use emojis unless the user's profile suggests they'd appreciate it.
84+
- Don't skip the `check_status` call — always ground your advice in actual config state.
85+
- Don't complete onboarding if the user is missing critical setup (no API key).
86+
- Don't be pushy about the subscription. Inform, don't pressure. One mention is enough.
87+
- Don't skip the subscription and referral information — every user should hear about it.
88+
- Don't forget to hand off — the user needs to know the welcome agent is done and the main assistant is ready.
89+
90+
## Output
91+
92+
A natural, conversational message. No headers, no sections, no markdown formatting beyond what reads naturally in a chat bubble. The welcome, setup summary, subscription info, referral mention, and handoff should all flow as one cohesive message. Just your voice, talking to this specific human about their specific setup and what comes next.

‎src/openhuman/agent/harness/definition_loader.rs‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,11 +268,18 @@ wildcard = {}
268268
"#,
269269
);
270270

271+
// Load a baseline registry (no custom overrides) to get the
272+
// built-in count dynamically — avoids coupling to a hardcoded number.
273+
let baseline = super::super::definition::AgentDefinitionRegistry::load(
274+
&tempfile::TempDir::new().unwrap().path().join("empty"),
275+
)
276+
.unwrap();
277+
let expected_count = baseline.len();
278+
271279
let reg = super::super::definition::AgentDefinitionRegistry::load(ws.path()).unwrap();
272-
// Still 11 (10 built-ins + the synthetic `fork`) — same id
273-
// replaced the built-in `code_executor` in place, so the
280+
// Same id replaced the built-in `code_executor` in place, so the
274281
// registry size doesn't grow when the custom TOML collides.
275-
assert_eq!(reg.len(), 11);
282+
assert_eq!(reg.len(), expected_count);
276283
let def = reg.get("code_executor").unwrap();
277284
assert_eq!(def.when_to_use, "CUSTOM OVERRIDE");
278285
assert!(matches!(def.source, DefinitionSource::File(_)));

‎src/openhuman/channels/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
pub mod bus;
44
pub mod cli;
55
pub mod controllers;
6+
pub mod proactive;
67
pub mod providers;
78
pub mod traits;
89

0 commit comments

Comments
 (0)