When Your AI Agent Can Feel You Burning Out: Wiring Intelligence Between Systems
I built a personal intelligence platform and an AI agent team on my homelab. Until this week, they barely talked to each other. Here's how I wired them together, and why the connection matters more than either system alone.
I run two systems on my homelab Kubernetes cluster that, until this week, were shockingly bad at talking to each other.
The first is Life Hub: a personal intelligence platform I built with FastAPI, SQLite (now PostgreSQL with pgvector), and HTMX. It tracks my mood, sleep, habits, goals, relationships, finances, and a dozen other data streams. It has 200+ MCP tools, 15 data collectors pulling from Garmin, Spotify, GitHub, CalDAV, and more. It runs Granger causality tests on my data. It has a council of 8 AI advisors with distinct personalities who proactively comment on my life patterns. It scores my life across 8 pillars. It predicts my mood and energy for tomorrow. It detects anomalies, spots burnout risk, identifies relationship neglect, and classifies what "life season" I'm in.
The second is OpenClaw: an AI agent orchestration platform where Henry Calloway, my AI chief of staff, manages a team of 8 specialist agents. Henry decomposes my requests, delegates to Kit (the builder), Petra (the researcher), Thane (the security warden), and Margaux (the writer). He checks my calendar, scans my email, monitors my GitHub repos, watches cluster health, and delivers structured briefings via Telegram every morning and evening.
Life Hub is the brain. OpenClaw is the hands. And until this week, the brain was screaming into a void.
The Gap: Nobody Was Listening
Every 15 minutes, Life Hub's collector pipeline runs. It pulls data from 15+ sources, evaluates 22+ advisor trigger conditions, checks for anomalies, calculates burnout risk, and scores my 8 life pillars. When it detects something meaningful (mood dropping for three consecutive days, a habit streak about to break, a key relationship overdue for contact, burnout risk crossing from low to moderate), it creates an advisor comment in the database and sends an ntfy push notification to my phone.
That's it. A database record and a push notification.
Meanwhile, Henry (the agent who could actually do something about these signals) was oblivious. I dug into the data:
- Henry's morning briefing called 5 Life Hub MCP tools out of 200+ available
- His hourly heartbeat checked if collectors were stale, not what the data said
- His SOUL.md (core identity file) had zero instructions to monitor wellness
- His HEARTBEAT.md (proactive routine) listed calendar, email, GitHub, cluster health, and task board. No mood, no sleep, no habits, no relationships
- His ORCHESTRATION.md (delegation rules) had dispatch rules for security findings, research, and code. Nothing for life signals
- Across 300+ historical tasks, zero were created because Henry noticed a wellness signal
I had built a personal intelligence platform that generates genuinely useful insights, and an agent team that's supposed to be proactive, and they were operating in parallel universes. The intelligence existed. The capacity to act existed. The connection between them didn't.
The Design Principle: Data Plane vs. Control Plane
Before writing code, I needed a clear mental model. The answer came from network engineering: Life Hub is the data plane. OpenClaw is the control plane.
The data plane owns: collection, storage, analysis, scoring, and intelligence generation. It answers the question "what is happening?" It doesn't decide what to do about it.
The control plane owns: action, communication, orchestration, and distribution. It answers the question "what should we do?" It doesn't compute the underlying data.
This sounds obvious in retrospect, but the boundaries had drifted. Life Hub had action features that didn't belong in a data platform: it could publish blog posts to Ghost, send SMTP emails, push ntfy notifications, and manage a task queue. OpenClaw had data monitoring that belonged in the warehouse: it was querying the Kubernetes API directly and tracking AI token usage in session files.
But the boundary correction was the secondary goal. The primary goal was the nerve connection: making Life Hub's intelligence available to Henry in real-time so he could act on it.
Phase 1: The Nerve Connection
The foundation is a webhook system in Life Hub. I added a new module, app/webhooks.py, that implements fire-and-forget webhook delivery to registered targets. The targets are configured via a JSON environment variable:
[{
"url": "http://openclaw.openclaw.svc.cluster.local:3456/api/triggers",
"api_key": "...",
"events": null
}]When events is null, the target receives all event types. You can also subscribe to specific events if you only care about certain signals. The system defines six event types, each mapped to existing Life Hub intelligence:
trigger_fired: one of the 22+ advisor trigger conditions was met (mood drop, habit decline, goal stalled, relationship neglect, etc.)anomaly_detected: a z-score >2.0 statistical outlier on mood, sleep, habits, or activityburnout_risk_change: burnout risk level crossed a significant thresholdpillar_alert: a life pillar score dropped below 3.0 or a user-defined targetstreak_at_risk: a 5+ day habit streak hasn't been logged todaycollector_complete: the 15-minute collection pipeline finished with a summary of what changed
The webhook fires are async, fire-and-forget with a 5-second timeout. If Mission Control is down, the webhook silently logs a warning and Life Hub continues its work. This is critical: the data plane must never depend on the control plane's availability. A flaky network connection shouldn't prevent Life Hub from collecting data and evaluating triggers.
Wiring Into Existing Systems
The webhooks needed to fire from three integration points, each matching an existing Life Hub process:
Advisor trigger evaluation (advisor_triggers.py): After the trigger evaluation loop creates AdvisorComment records in the database, it now also fires a trigger_fired webhook for each triggered condition. The payload includes the trigger name, the advisor who triggered, the context summary, and the commentary: everything Henry needs to understand what happened without making additional API calls.
Notification checks (notifications.py): I refactored check_and_notify() to separate detection from delivery. Previously, the function early-returned if NTFY_URL wasn't configured, meaning no detection happened either. Now it always runs detection, fires webhooks for whatever it finds (streak at risk, mood missing, relationship overdue), and also sends ntfy notifications if configured. This means the webhook system works even without ntfy, and ntfy continues to work independently of webhooks.
Collector pipeline completion (collector_cli.py): After all 17 collectors run in parallel and the post-collection pipeline finishes (backups, digest refresh, trigger evaluation, notification check), a collector_complete webhook fires with the full summary: how many records each collector inserted, any errors, total elapsed time. This gives Henry a heartbeat signal that Life Hub is working correctly.
The Receiver: Severity Classification
On the OpenClaw side, Mission Control got a new POST /api/triggers endpoint. But not all signals are equal. A burnout risk change requires immediate attention, while a collector completion is informational at best. So the receiver classifies every incoming event into three severity tiers:
// P1: immediate action required
const P1_EVENTS = new Set(['burnout_risk_change']);
// Anomalies with extreme values also qualify
if (metric === 'mood' && val <= 3) return 'P1';
if (metric === 'sleep' && val <= 4) return 'P1';
// P2: process on next heartbeat
if (event_type === 'trigger_fired') return 'P2';
// P3: informational, batch into next briefing
return 'P3';P1 events auto-create an urgent task on Henry's board. This is the key innovation: when Life Hub detects something critical, it doesn't just log it. Mission Control creates a task assigned to Henry with priority "high", domain "wellness", and a description that tells him exactly what to check and how to respond. Henry picks it up on his next heartbeat (every hour) and messages me via Telegram with context and a recommendation.
P2 events are stored in a rolling 7-day event file and queued for Henry's wellness check routine. P3 events are logged for historical reference and batched into the next briefing.
Supporting endpoints round out the system: GET /api/triggers/pending returns unacknowledged P2/P3 events, PATCH /api/triggers/:id/acknowledge marks them processed, and GET /api/triggers/history?days=N provides the full event timeline.
Phase 2: Teaching Henry to Care
The plumbing was in place. Now Henry needed to use it. This meant updating three markdown files that define his behavior. In the OpenClaw agent model, these files are the agent's brain.
SOUL.md: Core Identity
Henry's identity file got a new "Life Context Awareness" section. This isn't a feature. It's a fundamental shift in how he operates. Key directives:
- Before assigning heavy work, check
detect_burnout_risk()andpredict_energy() - When Steven seems off, check
get_mood_history(days=7)andget_wellness_score() - When prioritizing work, factor in
get_life_season(): crunch means protect energy, recovery means reduce load, growth means suggest deep work - Reference the Council of Advisors: 8 AI advisors monitor my data and notice patterns Henry might miss
- Use evidence, not vibes: call
get_correlations()andget_causal_insights()for data-backed recommendations
The morning briefing tone adapts based on wellness signals. High energy and good sleep? Ambitious goals, deep work suggestions. Low energy or burnout risk? Protective mode: suggest deferrals, highlight recovery habits. Social isolation detected? Suggest reaching out to specific people, by name, from the relationship health data.
HEARTBEAT.md: Hourly Routine
Henry's hourly heartbeat previously had six checks: calendar, email, GitHub, task board, cluster health, and "Life Hub status" (which just meant "are the collectors stale?"). The Life Hub check was the weakest: purely operational, zero wellness awareness.
The new "Life Hub Wellness Check" replaces it with a structured routine:
- Query
/api/triggers/pendingfor unacknowledged events - For each P2 event, call the relevant MCP tool to understand context:
mood_drop→get_mood_history(days=7): if declining 3+ days, message Stevensleep_decline→get_sleep_history(days=7): if <6h avg, suggest schedule adjustmenthabit_decline→get_habit_analysis(): identify which habits broke, suggest smallest recovery steprelationship_neglect→get_relationship_health(): name the person, suggest a concrete action
- For P3 events, accumulate and include in the next briefing
- Acknowledge each event after processing
The key design decision: Henry investigates context before alerting. He doesn't just forward the trigger data. He calls additional MCP tools to understand why and how bad before deciding how to communicate it. A mood drop might be a bad day (P3, mention in briefing) or a three-day trend (P2, direct message). The trigger tells him something happened; the follow-up tells him what to do about it.
ORCHESTRATION.md: Dispatch Rules
Henry's delegation rules got a new "Life Hub Signal Routing" section with explicit dispatch rules for each signal type. The most important principle:
Life Hub data informs priorities. If Steven is in a burnout risk state, don't assign heavy new work. If mood is dropping, check in before piling on tasks. If a relationship is overdue, suggest a concrete action. Don't just report the data point.
The rule for habit decline is instructive: "suggest the smallest recovery step. Don't pile on guilt." This matters because the wrong response to a broken habit streak is "you haven't exercised in 5 days." The right response is "your exercise streak is at risk. Even a 10-minute walk today keeps it alive." The agent's tone matters as much as its data.
The Enhanced Morning Briefing
The morning briefing cron job went from 5 Life Hub MCP tools to 12:
Previously:
get_morning_briefing, get_weather_intelligence, get_nudges,
get_calendar_intelligence, detect_burnout_risk
Now adds:
get_life_season, get_wellness_score, get_mood_history(days=3),
get_relationship_health, get_advisor_comments(days=1),
get_habit_analysis, predict_energyMore importantly, the briefing format changed. It now opens with life context before tasks:
🧭 Wednesday, March 26
You're in a growth season. Sleep averaged 7.2h (↑0.3h). Mood stable at 6.8. Burnout risk: low. Coach Miller notes your exercise streak hit 14 days. You haven't connected with Nathan in 12 days (cadence: weekly). Calendar: 3 meetings, 4h free. Energy forecast: 7/10.
[Then: tasks, email, GitHub, priorities, news]
Life context first. Work second. This is the briefing a chief of staff should deliver: not a task list, but situational awareness.
Phase 3: The Unified Action Surface
I had two competing task systems. Mission Control's board tracked agent work: what Kit was building, what Petra was researching. Life Hub's action queue tracked personal actions: habits to complete, goals to progress, mood to log, relationships to nurture. Each computed priorities independently. Neither knew about the other.
A new GET /api/actions/unified endpoint in Mission Control merges both sources into a single prioritized list. It pulls active MC tasks (backlog, in-progress, review) and calls Life Hub's get_action_queue() MCP tool for scored personal actions. Each item gets tagged with its source, mission_control or life_hub, so Henry (and I) can tell what's agent work versus personal.
The scoring is deliberate: MC tasks sort by priority within their category, Life Hub actions sort by a composite score that weighs streak protection (don't break a 14-day exercise streak), goal linkage (habits connected to active goals rank higher), pillar deficit (actions that boost low-scoring pillars rank higher), and time-of-day relevance.
Henry now has one view of everything I should be doing. No more "check Mission Control for agent tasks and Life Hub for personal actions."
Phase 4: Boundary Cleanup
With the core integration done, I cleaned up the boundary violations.
Ghost publishing: removed. Life Hub had an MCP tool called publish_weekly_to_ghost that took personal wellness digest data and published it as a blog post. My blog is for technical writing about my homelab, not automated dumps of my mood scores and habit completion rates. The tool is gone. The ghost_publisher.py module is deleted. The HTML rendering function (_digest_to_html) moved to email_digest.py where the email digest still uses it. The Ghost collector (which tracks blog activity as timeline events) remains: collecting data is data-plane work.
OpenClaw usage collector: added. A new collector in Life Hub pulls daily AI token usage from Mission Control's /api/usage endpoint. Now I can correlate "how much AI help I used today" against mood, productivity, and energy in Life Hub's analytics engine. Is there a correlation between high agent usage days and better mood? Between low usage and feeling overwhelmed? The data will tell me.
Kubernetes health collector: added. Another new collector fetches cluster health from Mission Control's existing /api/cluster endpoint (node status, pod counts, Flux reconciliation state) and stores it as InfraSnapshot records alongside Uptime Kuma data. This centralizes all infrastructure monitoring data in Life Hub's warehouse, and eventually lets Mission Control query Life Hub instead of the Kubernetes API directly.
The Network Plumbing
This runs on a Talos Linux Kubernetes cluster with Cilium. Default-deny network policies everywhere. Every new cross-namespace connection requires explicit policy.
The changes spanned six files in the home-ops repo:
- Life Hub network policies: Added CiliumNetworkPolicy egress from both the
life-hubapp and thelife-hub-collectorCronJob to theopenclawnamespace on port 3456 - OpenClaw network policies: Added Kubernetes NetworkPolicy ingress from the
life-hubnamespace to port 3456 - OpenClaw service: Exposed Mission Control port 3456 alongside the existing gateway port 80 (previously only accessible within the pod)
- Life Hub deployment: Added
WEBHOOK_TARGETSandWEBHOOK_API_KEYenvironment variables - Collector CronJob: Same webhook environment variables (the collector fires webhooks after collection completes)
- Life Hub secrets: Added
webhook-api-keyto the SOPS-encrypted secret
Not glamorous. But this is what production infrastructure actually looks like: explicit network policies, authenticated cross-service communication, and SOPS-encrypted secrets. Six YAML files so that one system can POST JSON to another system running 10 feet away on the same cluster.
Testing: 2,459 Tests, Zero Failures
Life Hub has a comprehensive test suite: 2,451 tests before this work. The webhook system added 17 new tests covering:
- Webhook fires to multiple targets
- Event type filtering (targets can subscribe to specific events)
- Graceful degradation on timeout, connection error, and non-2xx responses
- Payload structure validation
- Empty/invalid/non-JSON config handling
- No-authorization-header behavior when api_key is omitted
The OpenClaw usage collector added 9 tests covering success paths, error paths, empty responses, and title formatting. One existing notification test was updated: test_check_and_notify_skips_without_url became test_check_and_notify_runs_without_ntfy_url because the function now runs detection regardless of ntfy configuration.
Final count: 2,459 passed, 0 failed.
The Feedback Loop
Here's why this matters beyond the technical implementation.
Life Hub alone is a great dashboard. I can see my mood trends, my habit streaks, my pillar scores. But I have to look at it. I have to remember to check. And on the days when my mood is low and my energy is depleted (exactly the days when I most need the insights), I'm least likely to open the dashboard.
OpenClaw alone is a capable agent team. Henry can check my calendar, triage my email, and manage my task board. But without Life Hub's data, he's operating on the surface. He knows I have three meetings today but doesn't know I slept 4 hours. He knows there are tasks on the board but doesn't know my burnout risk is elevated.
Together, they form a feedback loop:
Data → Insight → Action → Outcome → New Data
I sleep poorly. Garmin reports 4.5 hours. Life Hub's collector ingests it, the trigger evaluation detects sleep below the 25th percentile baseline for three days, and fires a trigger_fired webhook. Mission Control classifies it as P2. On Henry's next heartbeat, he checks /api/triggers/pending, calls get_sleep_history(days=7), sees the declining trend, and messages me: "Sleep has averaged 5.1h over the last 3 days. Coach Miller suggests protecting your energy today. Consider deferring the architecture review to Thursday." I take the advice. I sleep better. The next morning's data reflects the improvement.
No system in isolation can do this. The dashboard can detect the problem. The agent can suggest the solution. But only the connection between them closes the loop.
What's Next
The webhook system is deployed but hasn't fired in production yet. Flux is reconciling the changes as I write this. The first real test will be the next collector run after the pods restart. I'm watching for several things:
Signal quality. Do the P1/P2/P3 severity classifications feel right? Will P1 events be rare enough to be meaningful, or will they fire too often and train me to ignore them? This is the alert fatigue problem (the same problem that plagues every monitoring system), and I expect to tune the thresholds.
Henry's response quality. Will the enhanced morning briefing feel meaningfully better with 12 MCP tools instead of 5? Will the wellness check on heartbeats add value, or will it be noise? The risk is that Henry becomes the system equivalent of an overprotective parent: "I noticed your mood was 6.5 instead of 7.0, should I cancel your afternoon?" The tone guidance in SOUL.md and ORCHESTRATION.md should prevent this, but real-world usage will tell.
Correlation discoveries. The OpenClaw usage collector creates a genuinely new data stream: AI token consumption per day. I'm curious whether there's a correlation between high agent usage and mood/productivity. My hypothesis is that high-agent-usage days are days when I'm delegating effectively and feeling in control. But the data might say otherwise.
The trust boundary. Right now, Henry can detect signals and message me. He can't take autonomous protective action: he can't block calendar time, defer tasks, or modify priorities without asking. As the system proves itself, I'll expand that boundary. The architecture supports it; the trust needs to be earned.
The Vision: A Personal Operating System
I want to be honest about what I think I'm building here, because I don't think it has a name yet.
It's not a dashboard. Dashboards are retrospective: they show you what already happened. It's not a chatbot. Chatbots are reactive: they wait for you to ask. It's not a digital twin. That term implies simulation. What I'm building is closer to a personal operating system: a system that continuously understands who I am, how I'm doing, and what I need, and has the autonomy to act on that understanding.
Consider what the system knows about me right now, running quietly on two nodes in my office:
- My sleep patterns over the last 6 months, with circular-time math that correctly averages midnight-crossing bedtimes
- My mood trajectory, with Granger causality tests that can tell me whether sleep actually predicts my mood or if the correlation is coincidental
- Which habits I'm most likely to skip on Wednesdays vs. Saturdays
- That my Connection pillar has been below 3.0 for two weeks and I haven't called Nathan in 12 days
- That my exercise streak is at 14 days and Coach Miller (one of 8 AI advisors who each have their own personality and accumulated memory) thinks this is the longest I've maintained it
- That I'm in a "growth" life season (high learning activity, stable sleep, active goals), and the system knows this because it classified my data patterns against a rule-based season detector
- What my energy will likely be tomorrow based on today's sleep, calendar load, and day-of-week patterns
- That Bramwell Finch (the contrarian advisor) flagged a "complacency check" because all my metrics have been trending positive for 4 days and he thinks I should be pushing harder
And now Henry knows all of this too. Not because I told him. Because the systems are connected.
The Council of Advisors is the piece that surprises people most. Eight distinct AI personalities: a strategist, a wildcard, a moral compass, a fixer, a historian, a perception architect, a wellness coach, and a devil's advocate, each with their own trigger conditions, their own voice, and their own accumulated memories from past interactions I rated highly. They're not chatbots I talk to. They're observers who monitor my data continuously and speak up when their domain is triggered. Sister Miriam notices when I haven't journaled with gratitude in a week. Jax Thorne flags when a goal has stalled for 7 days. Birdie Malone detects when I've been hyperfocused on code and haven't listened to music in days.
Before this week, their observations went into a database table. Now they flow through Henry, who can synthesize them into context-aware advice and deliver it at the right moment through the right channel.
Think about what this means for the morning briefing alone. Henry doesn't just read me my calendar. He opens with: "You're in a growth season. Sleep averaged 7.2h, up slightly. Mood stable. Burnout risk low. But Coach Miller notes you haven't connected with anyone socially in 5 days, and your Connection pillar is at 2.8. Nathan's relationship health score is 2.0. It's been 12 days. Your energy forecast for today is 7/10 based on last night's sleep and your calendar load. Alistair thinks this is a good day for deep work on the architecture review, but suggest reaching out to Nathan first: a 15-minute call would stabilize the Connection pillar."
That's not a briefing. That's a chief of staff who actually knows you.
The Trust Ladder
The system is designed to climb a trust ladder over time:
Level 1 (current): Detect and Report. Life Hub detects signals. Henry reports them. I decide what to do. This is where we are today: the nerve connection exists, the intelligence flows, but all action requires my approval.
Level 2 (next): Detect, Report, and Suggest. Henry doesn't just say "burnout risk is elevated." He says "I've drafted a schedule adjustment for Thursday: moved the architecture review to next week and blocked 2 hours for recovery. Approve?" The action plan is ready; I just approve or modify.
Level 3 (future): Detect and Act Within Boundaries. For certain categories (blocking personal time on the calendar when sleep drops below threshold, sending a check-in text to a neglected relationship, deferring low-priority tasks when energy is low), Henry has pre-approved authority to act. The trust boundary expands as the system proves its judgment.
Level 4 (aspirational): Anticipate and Prevent. The system doesn't wait for burnout risk to elevate. It notices the leading indicators (sleep declining for two days, calendar getting dense, habit completion dropping) and takes preventive action before I hit the threshold. This requires the causal inference engine to be right consistently, which requires months of data and tuning.
Each level requires the previous one to work reliably. You can't give an agent autonomous action authority if its detection is noisy. You can't trust its suggestions if its data is wrong. The architecture supports all four levels today. The trust will be earned incrementally.
Why This Has to Be Self-Hosted
This system contains the most intimate data about my life: my mood, my sleep, my relationships, my health metrics, my journal entries, my financial state. It runs Granger causality tests on the relationship between my sleep and my mood. It knows when I'm burned out before I do. It tracks which people I'm neglecting and how long it's been.
There is no universe where I send this data to a SaaS platform. Not because I distrust any specific company, but because the data itself is too personal to have a third party's Terms of Service apply to it. It runs on hardware I own, in my office, with network policies that prevent any data from leaving my cluster unless I explicitly configure an egress rule. The LLM calls go to external APIs (Anthropic, OpenAI), but the data stays local. The prompts contain synthesized summaries, not raw personal data.
Self-hosting isn't a compromise here. It's a requirement.
What Enterprise Gets Wrong
Enterprise platforms spend millions building "employee wellness" dashboards and "AI assistants." They fail for two reasons.
First, they separate data from action. The wellness platform shows you a burnout score. The task management platform manages your work. The calendar platform shows your schedule. They never talk to each other, so the burnout score can't influence the task priorities, and the calendar load can't feed into the burnout calculation. You end up with three dashboards that each tell you a piece of the story but can't help you with the whole picture.
Second, they optimize for the organization, not the individual. A corporate wellness platform wants to reduce company-wide turnover. It doesn't care about your specific sleep patterns, your specific habit triggers, your specific relationship dynamics. It can't run Granger causality on your personal data because it doesn't have enough of it and doesn't have permission to try.
The personal operating system inverts both of these. Data and action are connected through a single nerve system. And it's optimized for exactly one person: me. The 8 advisors know my patterns, not aggregate patterns. The scoring engine weights metrics based on my personal baselines, not population averages. The agent team adapts to my life season, not a corporate calendar.
This is the architecture that actually helps. Not more dashboards. Not more AI chatbots. A closed loop between understanding and action, optimized for one person, running on hardware they control.
The Numbers
For the technically curious, here's what the full system looks like today:
- Life Hub: 200+ MCP tools, 38 ORM models, 33 UI pages, 15+ data collectors, 22+ proactive triggers, 8 AI advisors, 8 life pillars, 7 Pearson correlations + 10 Granger causal pairs, 2,459 tests passing
- OpenClaw: 9 agents (Henry + 8 specialists), Mission Control dashboard, Telegram integration, morning/evening briefings, hourly heartbeats, relationship tracker, calendar defender, pre-meeting briefer
- Infrastructure: 2-node Talos Linux K8s cluster, Flux GitOps, Cilium network policies, SOPS-encrypted secrets, Longhorn storage, Cloudflare Tunnel + Traefik ingress
- Cost: ~/month fixed (Claude Pro + ChatGPT Plus subscriptions), near-zero variable API costs thanks to subscription routing and pre-compute caching
- New in W10: 6 webhook event types, P1/P2/P3 severity classification, 4 new Mission Control endpoints, 12 MCP tools in morning briefing (up from 5), 2 new data collectors, 26 new tests
All of it runs on a Dell laptop and an Intel NUC sitting on a shelf. Total hardware cost: maybe . The software is all open source or self-hosted. The intelligence is real. And as of this week, it's finally connected end to end.
What Happened When We Deployed
The webhook system deployed cleanly. Flux reconciled, pods restarted, network policies applied. Within 15 minutes, the first collector_complete webhook events started arriving at Mission Control's /api/triggers endpoint. The nerve connection was live.
Then I opened Mission Control and nothing loaded.
The task board (the home page, the most important page) was blank. Just "Loading..." forever. Every other page that depended on the same shared JavaScript was broken too. The Council page showed advisor names but no commentary. The Usage page was blank. The Memories page was blank. The Agent Activity page was blank.
The root cause was something I hadn't anticipated: Henry's agents had been refactoring Mission Control's JavaScript for weeks. They'd been extracting functions from page-specific files into expected "shared globals" in app.js, but they never actually added those globals. The refactored code referenced functions like normalizeStatus, isStale, isOverdue, MS_PER_DAY, formatNumber, stripMarkdown, and priorityColor. None of them existed in app.js.
Every page crashed on load with ReferenceError: normalizeStatus is not defined. The JavaScript SPA router loaded app.js first (no errors there), then loaded the page-specific JS which immediately tried to call a function that didn't exist. The entire app was dead.
This was compounded by a second problem: the Mission Control deployment model. MC files live in a Kubernetes ConfigMap, but the running code lives on a PVC (persistent volume). An init container copies files from ConfigMap to PVC on first boot, then sets a marker (.mc-initialized) so subsequent restarts don't overwrite agent modifications. The agent-refactored JavaScript was on the PVC but never made it back to the ConfigMap. So every time we redeployed (which we did many times during the webhook work), the init container re-seeded from the ConfigMap (which had the old code), but the PVC still had agent-modified tasks.js that expected globals that didn't exist in the old app.js.
The fix was methodical: add every missing shared function to app.js, pull the agent-modified page files from the PVC to local git, sync everything to the ConfigMap, validate the YAML, bump the config hash, and redeploy. This took multiple cycles because each time we found new missing functions: MS_PER_DAY on the Council page, formatNumber on Usage, stripMarkdown on Memories, formatDateHuman on Daily Logs.
Then we discovered the Council page showed advisor names and trigger types but no commentary text. The page was looking for c.comment but the Life Hub API returns c.commentary. A field name mismatch: the agent who built the Council page used the wrong property name, and nobody caught it because the page rendered without errors (it just showed empty text).
Then the CSS. When we re-seeded from the ConfigMap, the Steven's Status widget in the sidebar (which shows mood and wellness scores) lost all its styling. The ConfigMap had one version of styles.css (the original). The PVC had a completely different version (an "OPS CENTER DESIGN SYSTEM v2" that the agents had built). They were incompatible: different CSS variable names, different class structures. We had to pull the pod's CSS, append the missing widget classes, and sync the merged version back.
Then the missing pages. The agents had created Quick Tasks, Wish List, Finance, R&D Lab, and Daily Logs pages, complete with frontend JavaScript and backend API routes. But the SPA router didn't have routes for them, the index.html didn't have nav items or script tags, and the server.js in the ConfigMap didn't have the API endpoints. All of this worked on the PVC (where agents had modified everything in place) but broke on every redeploy from ConfigMap.
This is the part of building with AI agents that nobody talks about: agents modify code on the filesystem, but deployment pipelines don't know about those modifications. There's no git commit, no PR, no ConfigMap update. The changes exist only on the PVC. Every deployment is a potential regression because the source of truth (ConfigMap) and the runtime state (PVC) have diverged.
The fix wasn't just technical. It was procedural. We pulled every agent-modified file from the PVC to local git, synced it all to the ConfigMap, and committed. We wrote a CLAUDE.md for the openclaw-workspace repo (which didn't have one!) documenting the shared globals, the deployment flow, and the gotchas. We updated the home-ops CLAUDE.md with ConfigMap sync warnings. Future sessions, whether human or AI, will know about this.
The Full QA Pass
After fixing the deployment issues, I ran a comprehensive QA pass using Playwright. Every page. Every API endpoint. Desktop and mobile viewports.
The results: 19 pages tested, 23 API endpoints verified, zero JS errors remaining.
The API test was simple: a Node.js script inside the MC container that hit every endpoint and checked whether it returned JSON or HTML. The SPA fallback (app.get('*')) catches any unrecognized route and returns the HTML shell, so any API endpoint returning HTML means the route isn't registered. Before our fixes, the todos, wishlist, and finance endpoints all returned HTML. After: all 23 returned JSON.
The Playwright testing was more interesting. For each page, I navigated, waited for data to load (some pages make MCP calls to Life Hub that take 10-15 seconds), checked the browser console for errors, and took screenshots. This caught the Council commentary field mismatch, the Memories stripMarkdown error, and several mobile layout issues: the Triggers page stats grid overflowed on iPhone-width viewports, tables didn't scroll horizontally.
The Team page got special attention. It's the most complex page in MC: an operational dashboard with agent status cards, a service ownership matrix, a pipeline visualization, model change control panel, coverage confidence score, and a collapsible Council of Advisors section. The QA found two bugs: the "Needs Attention" section was showing completed tasks (the filter checked priority but not status), and "The Architect" agent always showed DORMANT because the name-matching logic split "The Architect" on spaces and tried to match tasks assigned to "The" (zero matches). Both fixed with targeted patches.
Five New Capabilities
With the foundation stable, I added five capabilities that make Henry genuinely useful beyond task management.
1. Evidence-Based Habit Coaching. Henry now checks Life Hub's causal inference data on evening heartbeats (5-9 PM). When he finds a high-impact habit that hasn't been completed and has an active streak, he messages via Telegram with evidence: "Your meditation streak is at 6 days. On meditation days, your mood averages 7.1 vs 5.8 (causal link confirmed). Still time today." The key word is evidence. Not "maybe try meditating": data-backed, streak-aware, energy-adjusted coaching. On low-energy days, he suggests the easiest high-impact habit instead of the hardest.
2. Telegram Write-Back. When Steven tells Henry "just finished a run" in Telegram, Henry logs the habit to Life Hub via MCP (log_habit), confirms the streak count, and tells him the mood impact. Same for mood ("feeling 7/10"), sleep ("slept 6 hours"), thoughts ("idea: what if we..."), and goals. The Telegram conversation becomes the primary data entry surface: no need to open the Life Hub dashboard.
3. Calendar-Aware Energy Routing. On morning heartbeats (6-9 AM), Henry checks the energy forecast against the day's calendar. If energy is low and there's a demanding meeting, he suggests rescheduling: "Energy forecast 4/10 (slept 5.2h). Architecture review at 2 PM is heavy cognitive load. Want me to suggest Thursday? Your energy is historically 7+ on Thursdays." He uses Life Hub's predict_energy() and get_energy_patterns() for the data.
4. Experiment Suggestions. When Life Hub's data shows a consistent pattern (mood drops every Wednesday, or a strong correlation that hasn't been tested), Henry suggests a tracked experiment: "Your mood drops every Wednesday (avg 5.1 vs 6.8). Want to try a 2-week experiment: morning walk on Wednesdays? I'll create the experiment in Life Hub and run interrupted time-series analysis automatically." If Steven says yes, Henry calls create_experiment() and reminds him on the relevant days.
5. Weekly Synthesis. A new Sunday 7 PM cron job that's the most important message of the week. Henry calls 12+ Life Hub MCP tools (weekly digest, retrospective, life wheel, wellness history, mood distribution, relationship health, goal forecasts, habit optimization, causal insights, seasonal patterns, energy patterns, advisor comments, anomalies, experiments), runs a Council roundtable with Alistair (strategy), Coach Miller (wellness), and Bramwell (challenge), checks GitHub activity, and synthesizes everything into a structured review with a pillar scorecard, pattern analysis, and three priorities for the next week.
Killing ntfy
With the webhook pipeline working and Henry processing signals via Telegram, the ntfy push notification system was redundant. Steven was getting the same alerts twice: once from ntfy (dumb push, no context) and once from Henry (Telegram, with context and Council perspective).
We deprecated ntfy for all automatic notifications. The check_and_notify() function in Life Hub still runs detection (streak at risk, mood missing, habits not logged, relationship overdue), but instead of calling ntfy, it fires webhook events. Henry processes them on his next heartbeat and delivers via Telegram with evidence, tone adaptation, and Council advisor references.
The send_morning_briefing() function is now a no-op: Henry's morning cron handles this with 12 MCP tools instead of Life Hub's monolithic push. The send_notification() function still exists for manual pushes (the MCP tool) and backup failure alerts, but automatic notifications are entirely webhook-driven.
The notification flow went from Life Hub → ntfy → phone push (context-free) to Life Hub → webhook → Mission Control → Henry → Telegram (contextual, tone-adapted, Council-informed). Same signal, fundamentally different delivery.
Henry Builds a Page
The most interesting moment of the session happened when Steven asked Henry to build a "Today" page: a unified daily checklist that aggregates overdue items, time-sensitive actions, and upcoming deadlines into a single checkable list.
Henry designed it well. He proposed three options (new page, enhanced Quick Tasks, full command center), Steven chose Option A (dedicated page with real aggregation), and Henry started building. He tried spawning Claude Code (Opus) to write the code. It finished but produced no output: a "zombie process" where the model described what to do instead of doing it. He tried again. Same result.
So Henry built it himself. Directly. He wrote the server.js routes (action-items CRUD plus a /api/today aggregation endpoint), the today.js frontend (312 lines of vanilla JS with overdue/time-sensitive/today/upcoming sections), added the nav item and route. The page pulls from Quick Tasks, groups by urgency, shows category emojis and priority indicators, and has checkboxes for completion. It auto-refreshes every 60 seconds.
He couldn't restart the MC process (RBAC blocks kubectl exec from inside the pod), so Steven had to do the final deploy. But the code was solid: zero JS errors on first load, data rendered correctly, all interactions worked.
This is the part that matters: an AI agent designed a feature, attempted to delegate to a more capable model, handled the failure gracefully, fell back to building it directly, and produced working code. The code needed syncing to the ConfigMap (Henry can't do that from inside the pod), but the feature itself was complete and correct.
The Clean Code Pass
After 36 hours of building, fixing, and deploying, I ran a clean-code pass on Mission Control's 21 JavaScript files. The goal: consolidate the duplicate helpers that caused every bug, remove dead code, and prevent future breakage.
The findings were modest, a sign that the bug-fixing pass had already addressed the critical issues. Three true duplicates removed: priorityColor (identical in both app.js and tasks.js), escapeHtml (identical in both app.js and triggers.js), and teamDisplayStatus (identical to normalizeStatus despite a comment saying "intentionally different"). One dead route wired: home.js was registered as a page but had no SPA route.
Several locally-scoped duplicates were left intentionally: they have different implementations from their global counterparts. The tasks.js versions of isStale and isOverdue use local helpers (mapStatus, ts) that differ from the global versions. The sessions.js formatNumber formats as "1.2M"/"3.4K" while the global uses toLocaleString. Removing them would break functionality: different code, same name.
The most important deliverable wasn't code changes. It was the CLAUDE.md file created for the openclaw-workspace repo. This repo had no CLAUDE.md at all, despite being the source of every Mission Control bug. The new file documents the shared globals list, the deployment flow, the ConfigMap sync gotchas, and the pattern for adding new pages. Future Claude sessions (and future agent modifications) will have this context.
The Numbers (Updated)
After the full session:
- Life Hub: 200+ MCP tools, 38 ORM models, 33 UI pages, 17 data collectors (2 new: OpenClaw usage, Kubernetes health), 22+ proactive triggers, 8 AI advisors, 8 life pillars, 2,457 tests passing, 6 webhook event types
- OpenClaw: 9 agents (Henry + 8 specialists), 30+ active cron jobs, Mission Control with 20 pages (Today, Tasks, Agent, Calendar, Usage, Memories, Docs, Projects, Team, Council, Brain, Infra, Life-Hub, Triggers, Finance, Quick Tasks, Wish List, R&D Lab, Daily Logs, Home), 5 new agent capabilities (habit coaching, write-back, energy routing, experiments, weekly synthesis)
- Mission Control bugs found and fixed: 12 missing shared globals, 1 field name mismatch, 1 CSS design system merge, 5 missing API routes, 6 missing SPA route/nav/script entries, 3 duplicate helper removals, 1 YAML structure fix
- Infrastructure: 2-node Talos Linux K8s cluster, Flux GitOps, Cilium network policies with explicit cross-namespace webhook rules, SOPS-encrypted webhook API key
- Cost: ~$40/month fixed (Claude Pro + ChatGPT Plus), near-zero variable. Session cost for all this work: $0 (Claude Code on Max subscription)
- Commits this session: 30+ across 3 repos
- Blog post you're reading: also written and published during the session
All of it still runs on a Dell laptop and an Intel NUC. The intelligence is real. The nerve connection is live. The chief of staff is wellness-aware. And as of tonight, every page loads, every API returns JSON, and the morning briefing runs in 7 hours.