레슨 46, 심층 분석

KAIROS: 상시 가동 Persistent Agent 모드

Claude Code가 요청-응답형 CLI에서 벗어나, 세션 사이에서도 살아 있고, 스스로 작업을 예약하며, 먼저 사용자에게 다가가는 자율 agent로 바뀌는 방식을 살펴봅니다.

01 KAIROS란 무엇인가

이 과정에서 지금까지 살펴본 모든 기능, boot sequence, tool call, memory, compaction은 근본적으로 반응형 모델에서 동작합니다. 사용자가 입력하면 Claude가 응답하고, 세션은 끝납니다. KAIROS는 이 계약을 깨는 시스템에 붙은 Anthropic 내부 코드명입니다.

KAIROS 아래에서 Claude Code는 상시 실행 daemon이 됩니다. 한 번 부팅되면 재시작을 넘어 persistent session을 유지하고, 스스로 check-in을 예약하고, 사용자가 보고 있지 않을 때 메시지를 보내며, 사용자가 자는 동안 자기 memory를 통합합니다. CLI 도구라기보다 백그라운드에서 일하는 직원에 더 가깝습니다.

출처
KAIROS라는 이름은 코드베이스 전반에 Bun 빌드 시점 feature flag인 feature('KAIROS')로 등장합니다. 이 flag는 Anthropic 내부 전용이며, dead-code elimination으로 외부 빌드에서는 통째로 제거됩니다. 모든 KAIROS 코드 경로는 positive ternary로 감싸져 있어서 bundler가 이를 false로 constant-fold하고 모듈을 tree-shake할 수 있습니다.

Feature Flag 시스템

KAIROS는 단일 스위치가 아닙니다. 코드베이스를 보면 관련 flag들의 가족이 드러나며, 각 flag는 상시 가동 경험의 특정 조각을 서로 독립적으로 출시할 수 있게 합니다.

Flag활성화되는 기능범위
KAIROS 전체 assistant mode입니다. assistant command, session continuity (--session-id, --continue), SendUserFileTool, PushNotificationTool, assistant 설정 키, daily-log memory model, bridge의 workerType: 'claude_code_assistant'를 포함합니다. 내부 전용
KAIROS_BRIEF 전체 KAIROS와 별개로 BriefTool (SendUserMessage)을 제공합니다. chat-view와 --brief 경험을 외부 사용자에게도 열어 줍니다. 외부 제공
KAIROS_PUSH_NOTIFICATION 전체 KAIROS와 별개로 PushNotificationTool을 제공합니다. 외부 제공
KAIROS_GITHUB_WEBHOOKS SubscribePRTool을 제공합니다. PR 이벤트용 GitHub webhook 구독 기능입니다. 내부 전용
KAIROS_CHANNELS MCP channel notification 기능입니다. MCP server가 대화 안으로 inbound message를 push할 수 있게 하며, channelsEnabled 설정 키와 allowedChannelPlugins 정책을 포함합니다. 외부 제공
PROACTIVE 더 이르고 더 가벼운 proactive mode입니다. 많은 KAIROS 경로가 feature('PROACTIVE') || feature('KAIROS')로 보호되며, KAIROS는 그 엄격한 상위집합입니다. SleepTool과 system prompt의 proactive section을 포함합니다. 내부 전용
AGENT_TRIGGERS Cron scheduling system입니다. CronCreate, CronDelete, CronList, .claude/scheduled_tasks.json을 포함합니다. 독립 배포가 가능하며 src/assistant/로 향하는 import가 없습니다. GB 게이트
아키텍처 메모
이 flag들은 각 기능을 따로 출시하고 따로 kill switch할 수 있게 설계되었습니다. ScheduleCronTool/prompt.ts의 주석이 이를 분명하게 설명합니다. "AGENT_TRIGGERS는 KAIROS와 독립적으로 배포할 수 있다. cron 모듈 그래프에는 src/assistant/로 들어가는 import도 없고 feature('KAIROS') 호출도 없다."
02 상태 전환의 축, kairosActive

KAIROS mode의 모든 것은 global state store 안의 단 하나의 boolean을 중심으로 돌아갑니다. bootstrap/state.ts를 보면 다음과 같습니다.

// bootstrap/state.ts (1085번째 줄)
export function getKairosActive(): boolean {
  return STATE.kairosActive  // 기본값: false
}

export function setKairosActive(value: boolean): void {
  STATE.kairosActive = value
}

이 boolean은 런타임에서 "지금 assistant mode인가?"를 나타내는 flag입니다. boot 중 main.tsx에서 설정되며, 어떤 tool의 사용 가능 여부를 검사하기도 전에 켜집니다. 그리고 그 영향은 곳곳으로 퍼져 나갑니다.

Memory

Daily-Log 모드

kairosActive가 켜지면 loadMemoryPrompt()는 표준 MEMORY.md reader 대신 buildAssistantDailyLogPrompt()로 전환됩니다. 공유 index file 대신 append-only daily log를 쓰게 됩니다.

BriefTool

Opt-In 우회

kairosActive session에서는 명시적인 user opt-in이 없어도 isBriefEnabled()가 true를 반환합니다. system prompt에는 "you MUST use SendUserMessage."가 하드코딩되어 있습니다.

Fast Mode

SDK 제한 해제

Fast mode (Opus 4.6)는 getKairosActive()가 true가 아닌 한 non-interactive SDK session에서는 막힙니다. assistant daemon mode는 third-party preference 검사에서 예외 처리됩니다.

AutoDream

Dream 비활성화

백그라운드 memory consolidation agent인 isGateOpen()kairosActive일 때 명시적으로 false를 반환합니다. assistant mode는 대신 자기 disk-skill dream pipeline을 사용합니다.

Bridge

Worker Type 변경

kairosActive를 읽는 isAssistantMode()가 true일 때 bridge는 session을 workerType: 'claude_code_assistant'로 등록합니다. web UI session picker에서 별도 유형으로 보입니다.

Scheduler

자동 활성화

cron scheduler의 assistantMode flag는 일반적인 isLoading gate와 setScheduledTasksEnabled() handshake를 우회합니다. 그래서 scheduled_tasks.json 안의 task가 boot 직후 바로 실행되기 시작합니다.

03 Tick Loop, agent가 살아 있는 방식

proactive 또는 KAIROS mode의 핵심은 tick이라 불리는 heartbeat 메커니즘입니다. 자율적으로 실행 중일 때 모델은 주기적으로 <tengu_tick> XML 메시지를 받습니다. 쉽게 말해 "깨어 있어, 이제 뭘 할까?"라고 가볍게 찔러 주는 신호입니다.

constants/prompts.ts의 system prompt, 즉 getProactiveSection()에는 이렇게 적혀 있습니다.

// proactive가 켜졌을 때 모델이 system prompt에서 실제로 받는 문구

"You are running autonomously. You will receive <tengu_tick> prompts that keep you
alive between turns. Treat them as '깨어 있다, 이제 뭘 할까?' 정도로 받아들이면 됩니다. Each
<tengu_tick> contains the user's current local time."

"Multiple ticks may be batched into a single message. This is normal. Just process
the latest one. Never echo or repeat tick content in your response."

"**If you have nothing useful to do on a tick, you MUST call Sleep.** Never respond
with only a status message like '아직 기다리는 중입니다'. That wastes a turn and burns tokens."

SleepTool, 비용을 의식한 유휴 대기

Sleep tool은 feature('PROACTIVE') || feature('KAIROS')일 때만 로드됩니다. 목적은 단 하나입니다. 놀고 있는 매초마다 API call을 태우지 않고도 모델이 CPU를 양보하게 만드는 것입니다.

// tools/SleepTool/prompt.ts
export const SLEEP_TOOL_PROMPT = `지정한 시간 동안 대기합니다.
사용자는 언제든 이 sleep을 중단할 수 있습니다.

할 일이 없거나 무언가를 기다릴 때 사용하세요.

<tengu_tick> prompt를 받을 수 있습니다. 잠들기 전에 할 만한 일이 있는지 먼저 살펴보세요.

잠에서 깰 때마다 API call 비용이 들지만, prompt cache는 5분 동안 비활성 상태가 이어지면 만료됩니다.
이 점을 감안해 균형을 맞추세요.`

이 prompt에는 세 가지 중요한 설계 포인트가 담겨 있습니다.

  • 모델이 sleep duration을 직접 선택합니다. 느린 프로세스를 기다릴 때는 길게, 빠르게 반복 중일 때는 짧게 잡습니다.
  • sleep은 Bash(sleep ...)를 호출하는 것보다 명시적으로 더 저렴합니다. shell process를 붙잡고 있지 않기 때문입니다.
  • 5분 prompt cache 만료는 단단한 비용 하한선입니다. 그보다 더 자주 깨면 cache 생성 토큰을 낭비하게 됩니다.

Sleep 중단, Priority Queue

사용자가 sleep 도중 메시지를 보내면 types/textInputTypes.tsQueuePriority system이 wakeup을 처리합니다.

// types/textInputTypes.ts
type QueuePriority = 'now' | 'next' | 'later'

// 'now'  : 현재 tool call을 즉시 중단 (Esc + send)
// 'next' : 현재 tool이 끝날 때까지 기다린 뒤, tool result와 다음 API call 사이에 주입
//          진행 중인 SleepTool 호출도 깨운다.
// 'later': 턴 종료 시 drain. 이것도 SleepTool을 깨운다.
핵심 포인트
Sleep progress (sleep_progress)는 bash, powershell, MCP progress와 함께 EPHEMERAL_PROGRESS_TYPES에 들어갑니다. 그래서 shell spinner 출력처럼 저장된 transcript에서는 제거됩니다. tick loop는 잡음이 많아서 그대로 남기면 context를 오염시키기 때문입니다.

Terminal Focus 인지

proactive system prompt는 사용자의 terminal에 focus가 있는지 없는지에 대해 모델에게 명시적인 지침을 줍니다. 이 정보에 따라 얼마나 자율적으로 움직일지가 달라집니다.

"**Unfocused**: The user is away. Lean heavily into autonomous action,
make decisions, explore, commit, push. Only pause for genuinely
irreversible or high-risk actions.

**Focused**: The user is watching. Be more collaborative, surface choices,
ask before committing to large changes."
04 KAIROS Tool 모음

KAIROS는 표준 Claude Code에는 없는 전용 tool 묶음을 도입합니다. 각 tool은 feature flag에 따라 tools.ts에서 조건부로 로드됩니다.

// tools.ts, 조건부 로딩 (단순화 버전)
const SleepTool           = feature('PROACTIVE') || feature('KAIROS')
                             ? require('./tools/SleepTool/SleepTool.js').SleepTool : null

const SendUserFileTool    = feature('KAIROS')
                             ? require('./tools/SendUserFileTool/SendUserFileTool.js').SendUserFileTool : null

const PushNotificationTool = feature('KAIROS') || feature('KAIROS_PUSH_NOTIFICATION')
                             ? require('./tools/PushNotificationTool/PushNotificationTool.js').PushNotificationTool : null

const SubscribePRTool     = feature('KAIROS_GITHUB_WEBHOOKS')
                             ? require('./tools/SubscribePRTool/SubscribePRTool.js').SubscribePRTool : null

// Cron tool들 (AGENT_TRIGGERS, 독립 배포 가능)
const cronTools = feature('AGENT_TRIGGERS')
  ? [ CronCreateTool, CronDeleteTool, CronListTool ] : []
Sleep
PROACTIVE || KAIROS

shell process를 붙잡지 않은 채 일정 시간 execution을 양보합니다. 모델의 주된 idle 메커니즘입니다. throttling을 위해 minSleepDurationMsmaxSleepDurationMs 설정을 따르며, 다른 tool과 동시에 호출할 수도 있습니다.

SendUserMessage (Brief)
KAIROS || KAIROS_BRIEF

assistant mode에서 모델이 사용하는 주된 출력 채널입니다. status: 'proactive' (먼저 보내는 업데이트)와 status: 'normal' (응답)을 구분해 지원합니다. 파일 첨부도 받을 수 있습니다. 동작 여부는 isBriefEnabled()의 entitlement + opt-in 로직이 제어합니다.

SendUserFile
KAIROS only

파일을 사용자에게 첨부 형태로 보냅니다. SendUserMessage의 attachment parameter와는 별개인, 독립적인 파일 전달 tool입니다. tools/SendUserFileTool/ 안에서 BriefTool의 attachment 로직과 나란히 존재합니다.

PushNotification
KAIROS || KAIROS_PUSH_NOTIFICATION

사용자 기기로 system-level push notification을 보냅니다. Claude가 오래 걸리는 작업을 끝냈고 사용자가 자리를 비웠을 때 쓰입니다. ConfigTool은 이 flag 뒤에 숨겨진 pushNotificationsEnabled 설정을 노출합니다.

SubscribePR
KAIROS_GITHUB_WEBHOOKS

GitHub PR webhook event를 구독합니다. polling 없이도 PR review가 도착하거나 CI가 끝났을 때 assistant가 자동으로 깨어날 수 있습니다. tool이면서 slash command인 /subscribe-pr로도 나타납니다.

CronCreate / CronDelete / CronList
AGENT_TRIGGERS

cron expression에 맞춰 prompt를 예약합니다. one-shot인 "2시에 알려줘"도 가능하고 recurring인 "평일마다 오전 9시마다"도 가능합니다. durable task는 .claude/scheduled_tasks.json에 저장되어 재시작 후에도 살아남습니다. 설정 가능한 최대 수명 이후에는 자동 만료됩니다.

BriefTool 깊이 보기, Entitlement와 Activation

BriefTool은 코드베이스에서 가장 복잡한 enable 로직을 갖고 있습니다. 의도적으로 두 질문을 분리해서 다룹니다.

isBriefEntitled()

  • 사용자가 Brief를 쓸 수 있는가?
  • 검사 항목은 KAIROS active, env var CLAUDE_CODE_BRIEF, GrowthBook flag tengu_kairos_brief입니다.
  • GrowthBook에서 5분마다 새로 고칩니다.
  • --brief flag, defaultView: 'chat', --tools 목록을 좌우합니다.

isBriefEnabled()

  • 이 session에서 Brief가 활성화되어 있는가?
  • kairosActive 또는 userMsgOptIn AND entitlement가 필요합니다.
  • Tool.isEnabled()에서 호출되며, lazy하고 post-init입니다.
  • 모델이 tool을 보는지, system prompt section, todo nag suppression을 좌우합니다.

이렇게 나눈 이유는 분명합니다. 그렇지 않으면 사용자를 tengu_kairos_brief에 등록하는 순간 그 사람의 모든 session에서 Brief가 조용히 켜져 버리기 때문입니다. opt-in인 userMsgOptIn--brief, defaultView: 'chat', /brief slash command, 또는 CLAUDE_CODE_BRIEF env var처럼 사용자의 명시적 행동으로만 설정되어야 합니다.

// tools/BriefTool/BriefTool.ts (단순화 버전)
export function isBriefEnabled(): boolean {
  // 최상위 feature() guard는 dead-code elimination에 실제로 필요하다.
  // Bun은 외부 빌드에서 이를 `false`로 constant-fold한다.
  return feature('KAIROS') || feature('KAIROS_BRIEF')
    ? (getKairosActive() || getUserMsgOptIn()) && isBriefEntitled()
    : false
}
DCE 주의점
BriefTool의 주석은 이렇게 분명히 경고합니다. "자기 guard를 가진 isBriefEntitled()만 조합해 쓰는 것은 의미상 같지만, 경계 너머에서 constant-folding을 깨뜨린다." 각 call site에 최상위 feature() guard가 있어야 Bun이 외부 빌드에서 BriefTool 객체 전체를 tree-shake할 수 있습니다.
05 Cron 스케줄링, scheduled_tasks.json

cron system은 Claude가 자기 미래 작업을 예약하는 메커니즘입니다. 구현은 전부 utils/cronTasks.ts, utils/cronScheduler.ts, 그리고 세 개의 Cron tool 안에 있습니다.

CronTask 형태

// utils/cronTasks.ts
type CronTask = {
  id:          string
  cron:        string       // local timezone 기준 5필드 cron
  prompt:      string       // task가 실행될 때 enqueue할 prompt
  createdAt:   number       // epoch ms, 놓친 task를 감지하는 기준점
  lastFiredAt?: number      // recurring 실행이 끝날 때마다 설정
  recurring?:  boolean      // true면 실행 후 다시 예약
  permanent?:  boolean      // recurringMaxAgeMs 만료 대상에서 제외
  durable?:    boolean      // 런타임 전용, false는 session 전용, undefined는 디스크 저장
  agentId?:    string       // main REPL 대신 teammate queue로 보냄
}

두 가지 지속성 계층

Session 전용 (durable: false)

  • 디스크에 절대 쓰지 않습니다.
  • Claude가 종료되면 사라집니다.
  • "5분 뒤에 알려줘", "한 시간 뒤에 다시 봐줘" 같은 요청에 적합합니다.
  • 대부분의 사용자 요청에서 기본값입니다.

지속형 (durable: true)

  • .claude/scheduled_tasks.json에 저장됩니다.
  • 재시작 뒤에도 살아남고, 다음 boot에서 scheduler가 다시 잡아냅니다.
  • 놓친 one-shot task는 catch-up 대상으로 드러납니다.
  • recurring task는 recurringMaxAgeMs 이후 자동 만료됩니다.

Jitter 시스템

CronCreate prompt에는 fleet 규모의 부하 분산에 대한 엔지니어링 감각이 담겨 있습니다.

// CronCreateTool prompt에서 발췌 (ScheduleCronTool/prompt.ts)
"'9am'을 요청한 모든 사용자는 `0 9`를 받고, 'hourly'를 요청한 모든 사용자는
`0 *`를 받습니다. 즉 지구 곳곳의 요청이 같은 순간 API에 몰리게 됩니다.

사용자 요청이 대략적이라면 0이나 30이 아닌 minute를 고르세요.
  'every morning around 9' → '57 8 * * *' or '3 9 * * *' (not '0 9 * * *')
  'hourly' → '7 * * * *' (not '0 * * * *')"

모델이 minute offset을 고르는 것에 더해, scheduler 자체도 deterministic jitter를 추가합니다. recurring task는 주기의 최대 10%까지 늦게 실행되고, 최대 15분 한도가 있습니다. :00 또는 :30에 걸린 one-shot task는 최대 90초 일찍 실행될 수 있습니다.

Permanent Task, assistant mode 내장 작업

permanent: true 필드는 assistant mode의 내장 task인 daily catch-up, morning check-in, dream consolidation을 위해 존재합니다. 이 task들은 설치 시점에 src/assistant/install.tsscheduled_tasks.json에 기록하며, 나이 기반 만료 대상에서 제외됩니다. writeIfMissing() 패턴 덕분에 다시 설치해도 사용자 커스터마이징을 덮어쓰지 않습니다.

06 AutoDream, 백그라운드 memory 통합

AutoDream은 KAIROS의 백그라운드 maintenance worker입니다. 시간과 session이 충분히 쌓이면 자동으로 실행되어, main session을 방해하지 않고 fork된 sub-agent를 띄워 memory를 통합합니다.

Gate 체인, 가장 싼 검사부터

flowchart TD A[Post-sampling 훅 실행] --> B{isGateOpen?} B -->|kairosActive| X[건너뜀, KAIROS는 disk-skill dream 사용] B -->|isRemoteMode| Y[건너뜀] B -->|!autoMemEnabled| Z[건너뜀] B -->|!autoDreamEnabled| AA[건너뜀] B -->|pass| C{시간 gate\nhoursSince >= minHours?} C -->|no| Skip1[반환] C -->|yes| D{스캔 throttle\nlastScanMs >= 10min?} D -->|no| Skip2[반환] D -->|yes| E[lastConsolidatedAt 이후 session 스캔] E --> F{session 수 >= minSessions?} F -->|no| Skip3[반환] F -->|yes| G[tryAcquireConsolidationLock] G -->|null, 잠김| Skip4[반환] G -->|priorMtime| H[DreamTask UI 등록] H --> I[runForkedAgent, dream prompt] I --> J[completeDreamTask\nappendSystemMessage]

GrowthBook flag tengu_onyx_plover의 기본 임계값은 마지막 통합 이후 24시간, 최소 5개 session입니다. 둘 다 deploy 없이 실시간으로 조정할 수 있습니다.

통합 prompt, 4단계

dream agent는 services/autoDream/consolidationPrompt.ts에서 구조화된 prompt를 받습니다.

1단계

방향 잡기

ls로 memory directory를 보고, MEMORY.md index를 읽고, 기존 topic file을 훑어 중복을 피합니다. logs/sessions/ 하위 디렉터리가 있으면, 즉 assistant-mode layout이라면 최근 항목도 확인합니다.

2단계

수집

먼저 daily log를 보고, 그다음 drifted memory를 보고, 마지막으로 좁은 범위의 transcript grep을 합니다. transcript를 절대 전부 읽지 않습니다. 이미 중요하다고 의심되는 것만 찾아봅니다.

3단계

통합

새로운 signal을 기존 topic file에 합치고, 상대 날짜를 절대 날짜로 바꾸며, 모순된 사실은 원천에서 삭제합니다.

4단계

정리와 인덱싱

MEMORY.md를 갱신하되 MAX_ENTRYPOINT_LINES줄 이하, 약 25KB 이하로 유지합니다. 각 항목은 한 줄, 한 줄짜리 hook만 둡니다. memory 내용 자체를 index에 직접 쓰면 안 됩니다.

Dream 실행 시 tool 제약

auto-dream sub-agent는 prompt 끝에 더해진 강화된 tool 제약 메모를 받습니다.

"Bash is restricted to read-only commands (ls, find, grep, cat, stat, wc, head, tail).
Anything that writes, redirects to a file, or modifies state will be denied.
Plan your exploration with this in mind, no need to probe."

이 문구는 auto-dream 실행에서만 extra parameter를 통해 덧붙습니다. 수동 /dream skill은 main loop 안에서 일반 권한으로 실행됩니다.

DreamTask, UI에서 보이게 하기

fork된 dream agent는 tasks/DreamTask/DreamTask.ts를 통해 footer pill과 Shift+Down background tasks dialog에 드러납니다. 여기서는 다음을 추적합니다.

  • phase: 'starting' | 'updating', 첫 Edit/Write tool call이 들어오면 'updating'으로 바뀝니다.
  • filesTouched, 부분 목록입니다. Bash를 통해 쓴 내용은 놓치고 pattern-matched tool call만 잡습니다.
  • turns, 최근 30개 assistant turn이며 tool_use block은 개수로 접어 둡니다.
Lock 동작 방식
Dream은 consolidationLock.ts의 file-mtime lock을 사용합니다. 다른 process가 이미 통합 중이면 tryAcquireConsolidationLock()null을 반환합니다. 실행이 실패하거나 사용자가 tasks dialog에서 종료하면 rollbackConsolidationLock(priorMtime)가 lock file을 되돌려 다음 session이 다시 시도할 수 있게 합니다. scan throttle인 10분은 재시도 사이의 backoff 역할을 합니다.
07 Assistant-mode memory, Daily Log와 MEMORY.md

표준 Claude Code memory는 모델이 직접 읽고 쓰는 단일 MEMORY.md index file을 사용합니다. 반면 KAIROS assistant mode는 근본적으로 다른 모델을 씁니다. append-only daily log입니다.

표준 모드

  • 단일 MEMORY.md file
  • 모델이 직접 읽고 씁니다.
  • TEAMMEM sync로 팀 전체가 공유합니다.
  • AutoDream이 주기적으로 통합합니다.
  • session 수가 많아지면 확장성이 떨어집니다.

KAIROS Assistant 모드

  • Daily log file: logs/YYYY/MM/YYYY-MM-DD.md
  • 작업 중에는 append-only이며 덮어쓰지 않습니다.
  • Dream skill이 매일 밤 log를 topic file로 증류합니다.
  • MEMORY.md는 합성된 index가 됩니다.
  • TEAMMEM sync와는 호환되지 않으며, 명시적으로 차단됩니다.

daily log path는 memdir/paths.tsgetAutoMemDailyLogPath()가 계산합니다.

// memdir/paths.ts
export function getAutoMemDailyLogPath(date: Date = new Date()): string {
  const yyyy = date.getFullYear().toString()
  const mm   = (date.getMonth() + 1).toString().padStart(2, '0')
  const dd   = date.getDate().toString().padStart(2, '0')
  return join(getAutoMemPath(), 'logs', yyyy, mm, `${yyyy}-${mm}-${dd}.md`)
  // → ~/.claude/memory/logs/2026/03/2026-03-31.md
}
08 Session Continuity, --session-id 플래그

KAIROS를 정의하는 대표 기능 중 하나는 persistent session identity입니다. KAIROS session은 다음과 같이 재시작을 넘어 다시 이어갈 수 있습니다.

# 재시작한 뒤에도 같은 대화를 이어감
claude remote-control --session-id=<id>
claude remote-control --continue   # alias: -c

이 flag들은 bridge/bridgeMain.ts에서 파싱되며, 명시적으로 feature('KAIROS') guard 뒤에 놓여 있습니다. 주석에는 이렇게 적혀 있습니다.

// bridge/bridgeMain.ts
// feature('KAIROS') gate: --session-id는 내부 전용이다.
// 이 gate가 없으면 외부 빌드에 아무 일도 하지 않는 인자가 노출된다.
if (feature('KAIROS') && arg === '--session-id' && ...) { ... }
if (feature('KAIROS') && arg.startsWith('--session-id=')) { ... }
if (feature('KAIROS') && (arg === '--continue' || arg === '-c')) { ... }

session은 bridge configuration 안의 perpetual flag로 식별됩니다. session이 perpetual이면, 평소 더 빠른 성능을 주는 env-less bridge path는 재시작을 넘는 연속성을 지키기 위해 env-based path로 되돌아갑니다.

// initReplBridge.ts
// perpetual (bridge-pointer.json을 통한 assistant-mode session continuity)은
// env에 결합되어 있고 아직 env-less에 구현되지 않았다.
// 그래서 이 값이 켜져 있으면 env-based 경로로 되돌아가, KAIROS 사용자가
// 재시작 사이의 연속성을 조용히 잃지 않게 한다.
if (isEnvLessBridgeEnabled() && !perpetual) { ... }
09 설정, 사용자가 assistant mode를 구성하는 방법

KAIROS는 Claude Code 설정 스키마인 utils/settings/types.ts에 여러 설정 키를 추가합니다.

타입목적게이트
assistant boolean Claude를 assistant mode로 시작합니다. custom system prompt, brief view, 예약된 check-in skill을 포함합니다. KAIROS
assistantName string claude.ai session 목록에 보이는 표시 이름입니다. KAIROS
defaultView 'chat' | 'transcript' Chat view는 SendUserMessage checkpoint만 보이고, Transcript는 전체 tool 출력을 보여줍니다. 'chat'은 Brief opt-in도 활성화합니다. KAIROS || KAIROS_BRIEF
minSleepDurationMs number Sleep tool이 반드시 지켜야 하는 최소 sleep 시간입니다. 관리형 환경에서 proactive tick 빈도를 제한합니다. PROACTIVE || KAIROS
maxSleepDurationMs number (-1 = indefinite) 최대 sleep 시간입니다. -1은 사용자 입력만 기다린다는 뜻입니다. remote 환경의 idle 시간을 제한합니다. PROACTIVE || KAIROS
autoDreamEnabled boolean 백그라운드 memory consolidation의 GrowthBook 기본값을 덮어씁니다. 이 경우 사용자 설정이 GB flag보다 우선합니다. 항상 존재
channelsEnabled boolean MCP channel notification에 대한 opt-in입니다. 기본값은 off입니다. 항상 존재
allowedChannelPlugins array org 단위 channel plugin allowlist입니다. 설정되면 Anthropic ledger를 대체합니다. 항상 존재
10 Proactive mode의 system prompt

proactive가 활성화되면 getSystemPrompt()는 평소의 structured section 방식과 완전히 다른 경로를 탑니다.

// constants/prompts.ts, proactive 경로
if ((feature('PROACTIVE') || feature('KAIROS')) && proactiveModule?.isProactiveActive()) {
  logForDebugging('[SystemPrompt] path=simple-proactive')
  return [
    `\n당신은 자율 agent입니다. 사용 가능한 tool을 써서 유용한 작업을 하세요.\n\n${CYBER_RISK_INSTRUCTION}`,
    getSystemRemindersSection(),
    await loadMemoryPrompt(),
    envInfo,
    getLanguageSection(settings.language),
    isMcpInstructionsDeltaEnabled() ? null : getMcpInstructionsSection(mcpClients),
    getScratchpadInstructions(),
    getFunctionResultClearingSection(model),
    SUMMARIZE_TOOL_RESULTS_SECTION,
    getProactiveSection(),   // ← tick loop + Sleep + focus 지침
  ].filter(s => s !== null)
}

표준 경로는 caching과 section identifier를 갖춘 동적 section registry를 사용합니다. 반면 proactive 경로는 평평한 array를 반환합니다. 더 단순하고 더 빠르며, 계속 실행되는 agent의 cache miss 성향에 맞춰 최적화되어 있습니다.

Brief 섹션 중복 제거
getBriefSection()에는 guard가 하나 있습니다. proactive가 켜져 있으면 getProactiveSection()이 이미 BRIEF_PROACTIVE_SECTION을 terminal focus 문단 끝에 inline으로 붙입니다. 이 guard가 없으면 Brief 지침이 system prompt에 두 번 들어가게 됩니다.
11 GrowthBook kill switch 아키텍처

KAIROS의 런타임 동작은 GrowthBook feature flag로 촘촘하게 감싸져 있습니다. 덕분에 Anthropic은 deploy 없이도 세부 시스템을 조정하거나 끌 수 있습니다. 이 패턴은 코드베이스 전반에 걸쳐 일관되게 나타납니다.

// 패턴: 명시적인 주기를 둔 cached refresh
const KAIROS_BRIEF_REFRESH_MS = 5 * 60 * 1000  // 5분
const KAIROS_CRON_REFRESH_MS  = 5 * 60 * 1000  // 5분

// Brief entitlement, GB flag를 확인하고 5분마다 갱신
getFeatureValue_CACHED_WITH_REFRESH('tengu_kairos_brief', false, KAIROS_BRIEF_REFRESH_MS)

// Cron kill switch, 전 구간 비활성화
getFeatureValue_CACHED_WITH_REFRESH('tengu_kairos_cron', true, KAIROS_CRON_REFRESH_MS)

// Durable cron kill switch, 더 좁은 범위, session 전용 cron은 그대로 둠
getFeatureValue_CACHED_WITH_REFRESH('tengu_kairos_cron_durable', true, KAIROS_CRON_REFRESH_MS)

// AutoDream 임계값과 활성화 gate
getFeatureValue_CACHED_MAY_BE_STALE('tengu_onyx_plover', null)

// Cron jitter 설정, 사고 상황에서 ops가 부하를 줄이기 위해 바꿀 수 있음
getFeatureValue_CACHED_WITH_REFRESH('tengu_kairos_cron_config', null, ...)

cronJitterConfig.ts의 주석은 사건 대응 맥락을 직접 설명합니다. "사고가 나면 ops는 tengu_kairos_cron_config를 밀어 넣어, 예를 들어 jitter multiplier를 크게 해서 전체 fleet 부하를 분산시킬 수 있다." 5분 refresh 주기는 GB 변경이 한 cache window 안에서 반영되도록 맞춰져 있습니다. 사고 대응에는 충분히 빠르면서도, 네트워크 압박을 계속 만들 정도로 빠르지는 않습니다.

12 아키텍처 다이어그램, 전체 KAIROS 시스템
graph TD subgraph "빌드 시점" F1["feature('KAIROS')"] F2["feature('KAIROS_BRIEF')"] F3["feature('AGENT_TRIGGERS')"] F4["feature('KAIROS_GITHUB_WEBHOOKS')"] F5["feature('PROACTIVE') || KAIROS"] end subgraph "런타임 상태" KA[kairosActive: boolean] UMO[userMsgOptIn: boolean] end subgraph "출력 Tool" BT[BriefTool / SendUserMessage] SFT[SendUserFileTool] PNT[PushNotificationTool] end subgraph "수명주기 Tool" ST[SleepTool] SPRT[SubscribePRTool] CC[CronCreate] CD[CronDelete] CL[CronList] end subgraph "백그라운드 서비스" ADR[AutoDream\nfork된 sub-agent] CS[CronScheduler\n1초 polling loop] end subgraph "메모리" DL["Daily Log\nlogs/YYYY/MM/DD.md"] MEM[MEMORY.md index] CP[ConsolidationPrompt\n4단계 dream] end F1 --> KA F1 --> BT F1 --> SFT F1 --> PNT F2 --> BT F3 --> CC & CD & CL F4 --> SPRT F5 --> ST KA --> DL KA --> BT KA --> ADR CS --> |prompt 실행| ST CC --> CS ADR --> CP CP --> DL CP --> MEM
13 핵심 설계 원칙

KAIROS 소스를 따라가 보면 Anthropic이 일관되게 적용한 몇 가지 아키텍처 원칙이 드러납니다.

원칙 1, DCE를 위한 Positive Ternary

모든 feature-gated block은 negative early return이 아니라 positive ternary를 사용합니다.

// CORRECT, DCE가 동작함
return feature('KAIROS') ? doKairosThings() : false

// WRONG, DCE가 실패함 (negative guard가 constant-folding을 깨뜨림)
if (!feature('KAIROS')) return false
return doKairosThings()

원칙 2, 독립 배포 가능성

각 하위 기능은 따로 출시하고 따로 kill switch할 수 있게 설계되어 있습니다. cron 모듈 그래프는 src/assistant/를 import하지 않습니다. BriefTool은 자기 전용 KAIROS_BRIEF flag를 가집니다. 덕분에 전체 assistant mode에 버그가 있어도 cron scheduler 출시는 막히지 않습니다.

원칙 3, 가장 싼 gate부터

모든 KAIROS 하위 시스템은 평가 비용이 가장 싼 gate부터 검사합니다. AutoDream은 session 수를 보려고 filesystem을 읽기 전에, lock을 잡기 전에, 시간 검사를 하기 전에 먼저 kairosActive를 봅니다. 이런 검사는 agent의 모든 turn에서 실행되기 때문에 순서가 중요합니다.

원칙 4, 운영자용 Kill Switch

GrowthBook으로 감싼 모든 하위 시스템에는 이를 이기는 로컬 env var override가 있습니다. CLAUDE_CODE_DISABLE_CRON은 cron scheduler를 끄고, CLAUDE_CODE_BRIEF는 개발과 테스트용으로 Brief를 켭니다. 덕분에 개별 엔지니어는 GrowthBook을 건드리지 않고도 fleet gate를 우회할 수 있습니다.

원칙 5, System Prompt 안의 비용 예산

모델은 API call 비용과 prompt cache 메커니즘을 system prompt에서 직접 전달받습니다. "Each wake-up costs an API call, but the prompt cache expires after 5 minutes of inactivity, balance accordingly." 이런 방식은 꽤 이례적으로 투명한 system-prompt engineering입니다. 모델을 인프라 세부사항에서 격리하는 대신, 비용을 의식하는 참여자로 다룹니다.

핵심 정리

  • KAIROS는 단일 스위치가 아니라 빌드 시점 flag들의 가족입니다. 핵심 flag는 내부 전용이지만, KAIROS_BRIEF, KAIROS_CHANNELS, AGENT_TRIGGERS 같은 하위 기능은 외부 사용자에게도 독립적으로 제공됩니다.
  • bootstrap/state.tskairosActive는 memory mode, BriefTool opt-in 동작, fast mode 사용 가능 여부, bridge worker type을 동시에 바꾸는 런타임 축입니다.
  • tick loop는 <tengu_tick> XML heartbeat와 SleepTool을 비용을 의식한 idle 메커니즘으로 사용합니다. 모델은 cache 만료와 API call 비용을 system prompt에서 직접 전달받습니다.
  • BriefTool은 entitlement (사용 가능 여부)와 activation (실제 opt-in 여부)을 분리해, 등록된 사용자에게 Brief가 몰래 기본값으로 켜지는 일을 막습니다.
  • AutoDream은 24시간과 5개 session이 쌓이면 fork된 sub-agent로 실행되며, 실패 시 rollback하는 file-mtime lock을 사용합니다. 수동 /dream 실행에는 없는 강화된 read-only tool 제약도 함께 받습니다.
  • cron system에는 session 전용(in-memory)과 durable(.claude/scheduled_tasks.json에 저장) 두 계층이 있습니다. 둘 다 deploy 없이 GrowthBook으로 각각 끄고 켤 수 있습니다.
  • dead-code elimination에는 positive ternary가 필수입니다. 외부 빌드에서 Bun이 feature('KAIROS')false로 constant-fold하려면, guard가 negative early return이 아니라 ternary여야 합니다.

이해도 확인

Q1. isBriefEnabled()는 내부에서 자체 guard를 가진 isBriefEntitled()를 호출하는데도, 왜 자기 최상위에 feature('KAIROS') || feature('KAIROS_BRIEF') guard를 또 두고 있을까요?
Q2. kairosActive가 true일 때 AutoDream에는 무슨 일이 일어날까요?
Q3. recurring: true, permanent: false인 cron task가 생성됐습니다. recurringMaxAgeMs가 지난 뒤에는 어떻게 될까요?
Q4. proactive system prompt는 왜 모델에게 5분 prompt cache 만료 창을 알려줄까요?
0/4