5.1 KAIROS: The Always-On Persistent AI Assistant
Source location:
src/assistant/,src/proactive/,src/services/autoDream/
Compile gate:feature('KAIROS')
Remote gate: GrowthBooktengu_kairos
KAIROS in one sentence
KAIROS turns Claude Code from a "chat tool" into a "long-running AI teammate":
- Even if the terminal is closed, Claude still runs in the background
- It writes a daily work log automatically
- Every 24 hours, it "dreams" to consolidate memory
- When no one is talking, it proactively finds useful work
The name comes from the Greek concept of "the right moment."
Activation flow
KAIROS only activates after a strict five-layer check:
1. feature('KAIROS') = true <- compile-time flag (always false in external builds)
↓
2. settings.json: assistant: true <- user explicitly enables it
↓
3. trusted directory check <- prevents malicious repo takeover
↓
4. GrowthBook: tengu_kairos = true <- Anthropic remote gate
↓
5. setKairosActive(true) <- persisted into global state
The --assistant CLI arg can bypass step 4 (used by Agent SDK daemon mode).
How persistent runtime works
Normal Claude Code: one-shot session
start -> wait for input -> process -> output -> exit
KAIROS Claude: persistent loop
start -> KAIROS activated
-> main loop does not exit, switches to listening mode
-> background durable process (via cron scheduler)
-> periodic tick checks (Proactive mode)
-> daily log write
-> auto Dream consolidation
Key state: kairosActive: boolean in src/bootstrap/state.ts. When true, the main-loop behavior changes.
Daily logs
KAIROS writes daily logs to:
<autoMemPath>/logs/YYYY/MM/YYYY-MM-DD.md
Example:
~/.claude/memory/logs/2026/04/2026-04-02.md
The content is generated by KAIROS and records daily summaries and key findings.
Dream mechanism
Dream is KAIROS's most sophisticated subsystem: a background sub-agent that consolidates scattered conversation artifacts into structured long-term memory.
Trigger conditions (three progressive checks)
// src/services/autoDream/autoDream.ts
function shouldDream(state: DreamState): boolean {
// Layer 1: time gate (cheapest check first)
if (hoursSinceLastDream(state) < minHours) return false
// Layer 2: session-count gate
if (newSessionCount(state) < minSessions) return false
// Layer 3: lock gate (prevent multi-process dream runs)
if (!acquireDreamLock()) return false
return true
}
minHours (default 24) and minSessions (default 5) are remotely tuned through GrowthBook tengu_onyx_plover.
Four-stage consolidation flow
┌──────────────────────────────────────┐
│ Stage 1: Orient │
│ · list memory directory │
│ · read MEMORY.md index │
│ · inspect existing topic files │
├──────────────────────────────────────┤
│ Stage 2: Gather │
│ · read new portions of daily logs │
│ · read existing memory topic files │
│ · read new turns from JSONL logs │
├──────────────────────────────────────┤
│ Stage 3: Consolidate │
│ · merge new signals into topics │
│ · convert relative dates to absolute dates │
│ · remove stale/inaccurate facts │
├──────────────────────────────────────┤
│ Stage 4: Prune │
│ · update MEMORY.md index │
│ · keep file sizes within limits │
└──────────────────────────────────────┘
Locking (concurrency protection)
src/services/autoDream/consolidationLock.ts uses a file lock to prevent concurrent Dream executions:
.consolidate-lock file:
- file exists = a process is currently dreaming
- file content = owner PID
- file mtime = lastConsolidatedAt timestamp
- double-write + re-read verification to avoid races
- PID liveness check (lock can be stolen after 1h timeout)
Proactive mode
When nobody is speaking, Claude proactively looks for work.
// src/proactive/index.ts
// three-state control
type ProactiveState = {
active: boolean // whether enabled
paused: boolean // paused by Esc (resumes on next user input)
contextBlocked: boolean // blocked on API errors (prevents tick-error loops)
}
Activation:
--proactiveCLI argCLAUDE_CODE_PROACTIVEenv var
When active, System Prompt appends:
# Proactive Mode
You are in proactive mode. Take initiative -- explore, act, and make progress
without waiting for instructions.
You will receive periodic <tick> prompts. Do whatever seems most useful,
or call Sleep if there's nothing to do.
SleepTool: "nap" tool for proactive runs
// SleepTool exists only with feature('PROACTIVE') || feature('KAIROS')
const SleepTool = feature('PROACTIVE') || feature('KAIROS')
? require('./tools/SleepTool/SleepTool.js').SleepTool
: null
If nothing useful can be done, Claude calls SleepTool to wait instead of burning ticks.
Background tasks (cron scheduler)
src/utils/cronScheduler.ts implements full task scheduling:
scheduler ticks every 1 second
↓
check .claude/scheduled_tasks.json
↓
trigger due tasks -> execute -> update next trigger time
Task types:
| Type | Description |
|---|---|
| One-off | removed after execution |
| Recurring | rescheduled after execution, expires in 7 days by default |
Permanent (permanent: true) | no 7-day expiration, KAIROS-specific |
Session-scoped (durable: false) | memory-only, disappears on process exit |
KAIROS core permanent tasks:
catch-up: resume interrupted workmorning-checkin: daily morning check-indream: automatic memory consolidation
UI manifestations
When KAIROS is active in terminal UI:
- Footer shows a "dreaming" pill
Shift+Downopens background task details dialog (DreamDetailDialog.tsx)- You can inspect progress and manually abort Dream