Switch models, not context.
milk is a terminal AI assistant that routes each prompt between a fast primary agent and a deep escalation agent — keeping the full conversation in sync across both. Start cheap. Go deep when you need it. Switch mid-workflow.
- Automatic routing — each prompt is classified and sent to the right agent without you changing tools
- Context handoff — when escalation fires, the primary conversation is reformatted as context; the escalation agent orients itself without a separate setup step
- Persistent memory — a Percept store survives across sessions; key facts are reinforced, decay, and promote to long-term memory over time (NREM consolidation)
- Built-in tools — the primary agent has bash, file read/write/edit, grep, find, HTTP GET, session access, and memory tools without any extra configuration
- Streaming TUI — bubbletea terminal UI with a scrollable transcript, live memory panel, status bar, and input history
- Aider and smolagents — plug in aider-chat or smolagents as either the primary or escalation agent
Both the primary and escalation roles support any of these backends — there is no backend tied exclusively to one role:
| Provider value | Backend |
|---|---|
"claude-cli" |
Claude Code CLI — runs claude as a subprocess; full tool access, session continuity, permission management |
omit / "" / "local" |
Any OpenAI-compatible server (llama.cpp, Ollama, LM Studio, Azure OpenAI, …) |
"bedrock" |
AWS Bedrock — native Converse API, SigV4 signing, credential auto-refresh |
"aider-cli" |
aider — milk calls the aider binary directly, no adapter needed |
"subprocess" |
Generic NDJSON subprocess (milk-smolagent adapter, bundled automatically) |
| anything else | Bearer-token HTTP (OpenRouter, Groq, Together.ai, GitHub Models, …) |
If no agent is configured, milk starts in setup mode. Use
/agent addto configure a backend interactively.
Each prompt passes through a decision chain:
- Explicit flags —
--escalateor--primaryoverride everything - Session state — if the escalation agent asked a follow-up, the next turn goes directly back to it
- Rules layer — hard thresholds (token length, keywords) then a weighted signal scorer
- Primary model classifier — the primary model decides
localorescalatewhen the scorer is inconclusive - Default — local
When the primary model cannot handle a task, it calls escalate(reason) and milk reformats the conversation history as context for the escalation agent.
Once escalation fires, auto-sticky keeps subsequent turns on the escalation agent (shown as <agent> (sticky) in the status bar) — avoiding the "cold-start" penalty on each turn. Use /primary to return to the primary agent.
milk exports OpenTelemetry signals to JSONL files under ~/.milk/otel/. The CLI exposes /metrics, /otel, /otel trim, and search_signals for inspection and maintenance.
/metricsshows the latest value for each metric+label combination./otelshows file sizes, record counts, and timestamp bounds./otel trimarchives the current files and recreates empty ones.search_signalssearches the raw JSONL files case-insensitively.
These commands are additive and do not require an external observability backend.
- Go 1.21+ (build from source only; pre-built binaries available)
- At least one configured agent backend (primary and/or escalation — each is optional; milk degrades gracefully if either is absent)
aider-chatpip package — only if using theaider-cliprovidersmolagents[litellm]pip package — only if using thesubprocess/smolagent provider
For a reference local setup (NVIDIA GPU, Ubuntu/WSL2, llama.cpp from source) see docs/setup.md. For provider-specific configuration see docs/providers.md.