5.1 KAIROS: The Always-On Persistent AI Assistant

Source location: src/assistant/, src/proactive/, src/services/autoDream/
Compile gate: feature('KAIROS')
Remote gate: GrowthBook tengu_kairos


KAIROS in one sentence

KAIROS turns Claude Code from a "chat tool" into a "long-running AI teammate":

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:

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:

TypeDescription
One-offremoved after execution
Recurringrescheduled 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:


UI manifestations

When KAIROS is active in terminal UI:


Next