# Brain

The Brain subsystem periodically consolidates session memory and re-evaluates long-running context — TUI plugin, server route, and headless scheduler.

## Overview

Brain is nikcli's "long-term memory" subsystem. While a session is running, every tool call, message, and decision lives in the session's JSON store; Brain's job is to periodically consolidate that signal into a single, well-organised `memory.instruction.md` file that future coding sessions can read at startup to orient quickly.

Brain is implemented in `packages/nikcli/src/brain/` and surfaces in three places: a headless scheduler that ticks every hour, a TUI feature plugin for the `brain` model picker, and a server route (`/brain`) plus a typed `httpapi/brain.ts` group for the Studio dashboard and external clients.

Experimental feature flag

Brain is disabled by default. Enable it by setting `experimental.brain` to `true` in `nikcli.json`, or turn on **Enable Brain** in Brain Settings.

## What Brain does

When triggered, Brain runs a real nikcli session under the hood — but a session with a very narrow scope. It:

1.  Reads the existing memory file (if any) under `.github/instructions/memory.instruction.md`.
2.  Pulls the most recent N sessions (default 10) and formats their message parts into a compact review.
3.  Sends the prompt (`Brain.buildBrainPrompt(memoryPath, reviews, currentMemory)`) to a dedicated session titled `BRAIN_SESSION_TITLE`, with permissions restricted to read/edit-only — no `task`, no `todo`, no `bash`.
4.  Cancels the session via `prompt.cancel` after `BRAIN_SESSION_TIMEOUT_MS = 5 minutes`.
5.  Diff-checks the memory file before/after. If it changed, marks the run as a success and writes the brain lock file (`Global.Path.state/.brain-lock`) so the next `shouldTrigger` check has a fresh anchor.

The consolidating model is configurable through `experimental.brainModel` (`"provider/model"`). When the configured model is unknown or unreachable, Brain silently falls back to the user's default model so the pass still runs.

## Trigger conditions

`Brain.shouldTrigger()` decides whether the scheduler should fire. It refuses to run when Brain or memory is disabled, when the cooldown has not elapsed, when a scan has happened in the last `SCAN_THROTTLE_MS = 10 minutes`, or when there aren't enough sessions since the last successful Brain pass.

| Constant | Default | Meaning |
| --- | --- | --- |
| `DEFAULTS.minHours` | `24` | Minimum hours between successful Brain passes (configurable via `experimental.brainMinHours`). |
| `DEFAULTS.minSessions` | `5` | Minimum new sessions since the last pass before Brain fires (configurable via `experimental.brainMinSessions`). |
| `SCAN_THROTTLE_MS` | `10 * 60 * 1000` | How often `shouldTrigger()` is allowed to scan session history. |
| `LOCK_DURATION_MS` | `1 hour` | Stale-lease duration for the `brain` flock lock. |
| `BRAIN_SESSION_TIMEOUT_MS` | `5 minutes` | Hard ceiling on a single Brain session; cancelled via `prompt.cancel` on expiry. |
| `SESSION_REVIEW_LIMIT` | `10` | How many recent sessions are included in the prompt. |
| `SESSION_REVIEW_MAX_CHARS` | `12_000` | Per-session review truncation length. |

The scheduler registers itself with `Scheduler.register({ id: "brain", interval: 60 * 60 * 1000, scope: "instance" })` — one tick per hour, scoped to the project instance.

Force runs bypass the throttle

`Brain.trigger({ force: true })` skips the `shouldTrigger` gate (except for the `enabled` and `memoryEnabled` checks) and reviews the most recent sessions even when no time has elapsed. The TUI dialog and the `/brain/trigger` route expose this path.

## Configuration

All Brain knobs live under `experimental` in `nikcli.json`. This example enables Brain:

nikcli.json

json 

1

2

3

4

5

6

7

8

9

```
{
  "experimental": {
    "brain": true,
    "memory": true,
    "brainMinHours": 24,
    "brainMinSessions": 5,
    "brainModel": "anthropic/claude-sonnet-4.5"
  }
}
```

| Field | Default | Meaning |
| --- | --- | --- |
| `experimental.brain` | `false` | Master switch for the Brain subsystem. When `false`, neither the scheduler nor a manual trigger will fire. |
| `experimental.memory` | `true` | Whether Brain is allowed to write the memory file. Turning this off keeps Brain's read path active but disables consolidation. |
| `experimental.brainMinHours` | `24` | Override `DEFAULTS.minHours`. |
| `experimental.brainMinSessions` | `5` | Override `DEFAULTS.minSessions`. |
| `experimental.brainModel` | (default provider/model) | "provider/model" string the consolidation session runs on. Falls back silently to the user's default model when unavailable. |

## Memory file

Brain writes to `.github/instructions/memory.instruction.md` inside the project directory (created on demand). The path is the same convention used by GitHub Copilot for project-level instructions, so the file is shared automatically with anything in the repo that reads `.github/instructions/*.md`.

The consolidating prompt focuses on four phases — orient, gather, consolidate, prune — and explicitly forbids creating a new memory file or asking the user questions. The full prompt template is in `Brain.buildBrainPrompt()`; the resulting memory file is meant to be concise, dated, and indexed for fast lookup.

Brain is also a long-term signal for other agents

Because the file lives under `.github/instructions/`, every future nikcli session in the same project picks it up automatically — it becomes part of the system prompt context for the next agent run.

## TUI & API surface

Brain is reachable from three places: the scheduler (passive), the TUI dialog (`dialog-brain.tsx` — pick the model and force a run), and the server routes.

dialog-brain.tsx

### TUI dialog

Bound to the `brain` feature plugin and surfaced from the command palette. Lets you pick the consolidation model and trigger a forced run.

server/httpapi/brain.ts

### Server routes

`GET /brain` returns the full status payload (enabled, memory enabled, min hours, min sessions, last brain at, hours since, sessions since, should trigger, configured model). `POST /brain/trigger` accepts `{force: boolean}` and runs `Brain.trigger`.

server/httpapi/brain.ts

### Typed HttpApi

Same surface under the typed HttpApi group (`operationId: brain.status`, `brain.trigger`) — mounted through `HttpApiBridge` when `NIKCLI_EXPERIMENTAL_HTTPAPI` is set. Used by the Studio dashboard and SDK clients.

Headless scheduler

The scheduler registers itself from `initBrainScheduler()` with `scope: "instance"`. The scheduler definition is in `packages/nikcli/src/scheduler/`; one tick per hour, per project instance.

## Source anchors

| Concern | File |
| --- | --- |
| Core service + consolidation prompt | `packages/nikcli/src/brain/index.ts` |
| Hourly scheduler | `packages/nikcli/src/brain/scheduler.ts` |
| Constants | `@nikcli-ai/util/brain-constants` |
| Classic routes | `packages/nikcli/src/server/httpapi/brain.ts` |
| Typed HttpApi | `packages/nikcli/src/server/httpapi/brain.ts` |
| TUI plugin | `packages/tui/src/feature-plugins/brain/index.tsx` |
| TUI dialog | `packages/tui/src/component/dialog-brain.tsx` |

---

Source: https://nikcli-ai.dev/docs/brain
