Introduction

Zenspace is your personal AI agentic workspace — a self-learning knowledge system that builds your wiki, remembers across sessions, and evolves with your decisions.

Most AI tools are chat boxes. You talk, they respond, and tomorrow it's gone. Zenspace takes a different path: your notes grow into a wiki. Your sessions build memory. Your patterns become wisdom.

The 10 Things That Make Zenspace Different

1. 📝 Notes That Build Themselves

Write normally — agents extract entities, auto-generate wiki pages, detect contradictions, and weave everything into a knowledge graph. Your notes become a living wiki, not a pile of files.

2. 🧠 Three Brains, One System

Short-term memory ("what just happened?") → Mid-term knowledge ("what do I know?") → Long-term wisdom ("what have I learned?"). Session context flows naturally upward. No cold starts.

3. 📈 It Learns From You

Daily reflection, weekly synthesis, Bayesian belief updates. The system spots recurring patterns, surfaces blind spots, and adapts to your workflow. Every session makes the next one sharper.

4. 🔄 Information → Knowledge → Wisdom

5-stage pipeline: Capture → Tidy → Organize → Distill → Fuse. From raw notes to polished wiki to deep wisdom. Data in, decisions out.

5. 🏛️ 13 Agents, Each an Expert

A Greek pantheon of AI specialists: Sisyphus orchestrates, Prometheus plans, Momus gatekeeps quality, Hephaestus executes, Hermes validates, Zeus judges. Each with the right model for the job.

6. 🎯 Smart Model Routing — No Manual Picking

Private data → local Ollama. Complex reasoning → Anthropic. Cost-sensitive → DeepSeek. Per-task fallback chains so nothing fails silently. One config, zero guesswork.

7. 💡 Built-in Decision Engine

Log decisions, compute expected value, set stop-loss lines, detect anti-patterns. The system doesn't just remember what you decided — it helps you decide better next time.

8. 📚 Seed Wisdom — 12 Mental Models + 21 Anti-Patterns

Pre-loaded thinking frameworks: Map ≠ Territory, Circle of Competence, Second-Order Thinking, Hanlon's Razor. Plus behavioral anti-pattern detection. Thinking tools, not just storage.

9. 🔗 Obsidian + OKF Dual Format

All notes are Obsidian-compatible Markdown with [[wikilinks]] and YAML frontmatter — open ~/.zen/vault/ directly in Obsidian, edit in both directions, no import/export. Under the hood, wiki pages follow OKF v0.1 (Open Knowledge Format): typed frontmatter (type: concept|reference|tool|...), bundle-relative links, and structured index files. Two formats, one knowledge base.

10. 🏠 Your Data, Your Rules

Local-first by default. macOS Keychain for secrets. Sensitive data never touches the cloud unless you explicitly allow it. Zero vendor lock-in.

How It Works (The 5-Stage Pipeline)

RAW NOTES ──► TIDY ──► ORGANIZE ──► DISTILL ──► FUSE
                                   │
                           Entity Extraction
                           Wiki Generation
                           Contradiction Detection
                                   │
                              WISDOM
                          (MEMORY.md + beliefs)
  1. Capture — Notes, RSS feeds, and raw files all go into inbox (PDF/Office/ebooks auto-convert to Markdown)
  2. Tidy — Clean, chunk, normalize into structured Markdown
  3. Organize — Embed, index, classify — make everything searchable
  4. Distill — Extract entities, compile wiki pages, detect contradictions
  5. Fuse — Synthesize wisdom, update beliefs, promote to long-term memory

Who Is It For?

  • Knowledge workers who want their notes to actively work for them
  • Thinkers who value structured decision-making and mental models
  • Privacy-conscious users who want AI without surrendering data
  • Power users who want to customize model routing across providers

Next: Installation

Installation

Prerequisites

macOS

  • macOS (Apple Silicon or Intel) — primary supported platform
  • Rust toolchain 1.80+ (only for building from source)
  • No additional dependencies — sandbox-exec is built-in

Linux

  • Rust toolchain 1.80+ (only for building from source)
  • bubblewrap (bwrap) — required for sandbox isolation
# Ubuntu/Debian
sudo apt install bubblewrap

# Fedora
sudo dnf install bubblewrap

# Arch
sudo pacman -S bubblewrap

Why bubblewrap? Zen uses Linux namespaces for sandbox isolation. Bubblewrap provides unprivileged sandboxing without requiring root access.

brew install savechina/tap/zenspace

Note: On Linux, Homebrew (Linuxbrew) also handles bubblewrap dependency automatically.

From Source

git clone https://github.com/savechina/zenspace.git
cd zenspace
bin/build
./target/release/zen --help

Binary Download

Download pre-built binaries from GitHub Releases:

macOS:

  • zen-{version}-aarch64-apple-darwin.tar.gz (Apple Silicon)
  • zen-{version}-x86_64-apple-darwin.tar.gz (Intel)

Linux:

  • zen-{version}-x86_64-unknown-linux-gnu.tar.gz (x86_64)
  • zen-{version}-aarch64-unknown-linux-gnu.tar.gz (ARM64)

Linux Note: After downloading, install bubblewrap:

sudo apt install bubblewrap  # Ubuntu/Debian

Post-Installation

Verify the installation:

zen version

Initialize your workspace:

zen workspace init

This creates the ~/.zen/ directory structure and default configuration.


Next: Quick Start

Quick Start

1. First Run — The TUI

zen

That's it. The first run initializes ~/.zen/ with the default directory structure and embedded configuration, then drops you into the interactive TUI — a chat session with your personal agent team.

2. Talk to Your Agent

Type naturally and hit Enter. The agent routes your request across 13 specialists (search, execution, planning, review) and streams the answer back.

KeyAction
EnterSend message
Shift+EnterInsert newline (multi-line input)
Ctrl+JInsert newline — tmux/SSH-safe fallback
Ctrl+DExit
PageUp / PageDownScroll history

Slash commands: type / to open the command popup. It filters as you type (matched characters highlighted, exact matches sorted first), groups commands by category, and Esc dismisses it. Use /tools to expand or collapse tool-call cards and /thinking to show reasoning blocks.

Rich output: tool calls stream in as collapsible cards (🔧 started / ✅ done with timing and hit counts), the footer shows ● working Ns while a turn runs, large pasted text collapses into a [Pasted N lines / M chars] pill, and diff/patch snippets render with red/green deltas.

3. Feed the Pipeline

Notes don't need a command — just drop files into the inbox. Markdown and plain text pass through as-is; PDF, Office documents (docx/xlsx/pptx), and ebooks (epub/odt/rtf) are converted to Markdown automatically on ingestion:

mkdir -p ~/.zen/vault/inbox
$EDITOR ~/.zen/vault/inbox/q3-planning.md
cp ~/Downloads/annual-report.pdf ~/.zen/vault/inbox/    # converted at intake

The background distill loop picks them up automatically: extracts entities, links them into the knowledge graph, compiles wiki pages, and archives the original. It runs while the TUI is open (or via the zen serve daemon) — no manual step.

Format notes:

  • PDF / Office convert in-process — no extra installs.
  • epub/odt/rtf convert via pandoc if it's on your PATH (brew install pandoc); without it those files stay queued and are picked up once installed — nothing is lost.
  • Files with no extractable text (e.g. scanned image-only PDFs) are quarantined rather than re-processed every cycle; originals are never modified.

4. Check System Health

zen doctor

Runs 8 liveness probes (config, database, memories, daemon, loop, provider, vault, outbox) and exits non-zero if anything needs attention. Add --json for machine-readable output.

5. See What It Built

zen wiki list

Browse the self-built wiki:

zen wiki show <page>
zen wiki lint        # orphan pages, broken wikilinks

Next Steps

CLI Commands Reference

Zen's CLI surface is intentionally small: the TUI plus 23 subcommands. Heavy capabilities (search, research, dispatch, briefs) run autonomously through the background scheduler and distill loop rather than as manual commands — drop notes into ~/.zen/vault/inbox/ and the system works on them.

General

CommandDescription
zenLaunch TUI (interactive agent session; first run initializes ~/.zen/)
zen versionShow version information
zen --helpShow full help with all commands

Chat & Sessions

CommandDescription
zen chatInteractive LLM chat (non-TUI)
zen session startStart an agentic session
zen session statusShow session status
zen session listList sessions
zen session archiveArchive a session

Knowledge Pipeline

CommandDescription
zen wiki listList wiki pages
zen wiki show <page>Show a wiki page
zen wiki reindexRebuild the knowledge index (FTS5 + embeddings; also heals legacy non-NFC alias rows; --dry-run preview, --fts-only resync)
zen wiki lintLint wiki (orphan pages, broken links, stale claims)
zen wiki distillRun the consolidation pipeline (inbox → wiki)
zen wiki rebuild-memoryRebuild the gateway memory store from Markdown + session archives (requires running daemon)
zen wiki loop runRun one self-learning consolidation cycle now
zen wiki loop statusLoop status (--json includes latency metrics)
zen wiki loop gapsShow pending gaps and user questions
zen wiki loop enable / disableToggle the background loop

Agents, Skills & Self-Learning

CommandDescription
zen agent listList registered agents
zen skill list --jsonList learned skills
zen skill run <name>Run a skill
zen skill progressShow skill learning progress
zen skill show <name>Show a skill's details
zen skill precipitateStage a distilled skill draft
zen skill confirm <name>Confirm a staged skill
zen discover runRun one self-learning cycle (stages 5b/5c/5d)
zen discover stage / queueStage and inspect improvement hypotheses
zen discover confirm / rejectReview staged hypotheses
zen discover reportLearning metrics (routing, stalls, calibration, replay)
zen discover arenaRun distill regression gate vs baselines
zen discover calibrate --writeDerive decision thresholds from labeled data
zen discover label status / next / setLabel supply workflow

System & Daemon

CommandDescription
zen serve startStart gateway daemon (--foreground, --http for loopback HTTP carrier, --mcp for stdio MCP mode)
zen serve statusDaemon health
zen serve stopStop daemon
zen serve testTest MCP connectivity
zen serve install / uninstallManage macOS launchd auto-start
zen doctorSystem health: 10 liveness probes (--json for scripts)
zen clean cacheClean caches (ungated)
zen clean all / trashDestructive cleanup — asks for confirmation unless --yes
zen logs <service>View structured logs
zen auditAudit log operations
zen sandbox testVerify sandbox isolation works

Workspace & Configuration

CommandDescription
zen workspace initInitialize ~/.zen/ workspace structure
zen config showShow effective configuration (layers merged)
zen provider listList available LLM providers
zen provider test <name>Test a provider connection
zen auth listList stored credentials
zen modelModel metadata + routing info

Utilities

CommandDescription
zen starter <template>Generate project scaffold from template
zen wps <action>Work process utilities
zen habit log / listHabit tracking
zen goal create / list / statusGoal management
zen plugin list / install / enable / disable / rehashPlugin management (WASM tools, MCP)

Quick Reference

# Daily workflow — all through the TUI
zen

# Or scripted
zen doctor                  # health check
zen wiki loop status        # is the learning loop alive?
zen wiki distill            # drain the inbox now
zen discover report         # what did the system learn?

# Maintenance
zen clean cache
zen serve status

Full reference: zen --help for the most up-to-date list of commands and flags.

Configuration Overview

Zen uses a 4-layer configuration system that merges settings from multiple sources. Higher-priority layers override lower ones, and you only need to specify the values you want to change.

Configuration Layers

PriorityLayerFile / SourceTypical Use
1 (highest)EnvironmentZEN_* environment variablesTemporary overrides, CI/CD, secrets
2Global User~/.zen/config.tomlUser-wide preferences
3Embedded Defaultconfig/config.toml (compiled in)Shipped defaults
4 (lowest)Built-in DefaultRust Default implsLast-resort fallbacks

Configuration is global-only: the .zen/ directory marks a project context (and sandbox allowlist) but is not a configuration layer. All config lives under ~/.zen/config.toml.

How Merging Works

Each layer merges cleanly into the previous one. A higher layer only overrides keys it explicitly sets — so you can override just default_model without copying the entire config.

# Example: ~/.zen/config.toml — just override what you need
default_provider = "deepseek"
default_model = "deepseek-v4-flash"

# Only the providers you want to customize
[providers.deepseek]
type = "openai-compatible"
base_url = "https://api.deepseek.com"
api_key = { env = "DEEPSEEK_API_KEY" }
default_model = "deepseek-v4-flash"

This minimal config merges with the embedded defaults — all other providers (Ollama, OpenAI, Anthropic, etc.) remain available from the embedded config.

Config Structure

The configuration is organized into these top-level sections:

SectionDescription
default_providerDefault provider name (references a [providers.*] key)
default_modelDefault model when no task-specific model is set
[providers.*]Named provider definitions: connection, model catalog, per-million token costs
[agents] / [agents.*]Agent tool grants + per-agent model routing (distill tasks and agent profiles)
[tui]TUI theme and knowledge-search mode (fast/full/off)
[history]Input history size cap
[embeddings]Local embedding provider, model, cache
[cron]Background schedules, timezone, LLM cost cap, TUI scheduler kill switch
[plugin]Plugin registry (learning, finance, custom plugins)
[mcp_servers.*]MCP client server connections
[sandbox]Network access + WASM sandbox permissions
[agentic.*]Agent engine tuning: loop, tool loop, review, delegation, retention, …
[channels.qqbot]QQ official-bot channel (WS gateway + REST sender)
[skills.auto_route]Automatic skill routing

Viewing Effective Configuration

# Show the complete merged configuration
zen config show

# List available providers
zen provider list

# Test a provider connection
zen provider test <provider-name>

The zen config show command displays the fully merged configuration from all layers, so you can always see exactly what's in effect.


Next: Provider Definitions

Provider Definitions

Providers are the connection endpoints to LLM services. Define them once by name in [providers.*] sections, then reference them in agent routing.

Supported Protocol Types

TypeAuth RequiredDescriptionExamples
ollamaNoLocal Ollama serverqwen3.6, llama3, mistral (local)
openaiAPI keyNative OpenAI APIgpt-4o, gpt-4o-mini
anthropicAPI keyNative Anthropic Messages APIclaude-sonnet-4-6, claude-haiku-4-5
geminiAPI keyGoogle Gemini APIgemini-2.0-flash
cohereAPI keyCohere APIcommand-r
mistralAPI keyMistral APImistral-large-latest
openai-compatibleAPI keyOpenAI-compatible endpointsDeepSeek, Groq, Perplexity, Aliyun, xAI
anthropic-compatibleAPI keyAnthropic-compatible endpointsMoonshot, MiniMax
mockNoTesting mock (no external calls)—

Basic Provider Configuration

# Local Ollama (no API key needed)
[providers.ollama]
type = "ollama"
base_url = "http://127.0.0.1:11434"
default_model = "qwen3.6:35b-mlx"

# OpenAI
[providers.openai]
type = "openai"
base_url = "https://api.openai.com/v1"
api_key = { env = "OPENAI_API_KEY" }
default_model = "gpt-4o-mini"

# Anthropic
[providers.anthropic]
type = "anthropic"
api_key = { env = "ANTHROPIC_API_KEY" }
default_model = "claude-haiku-4-5"

# OpenAI-compatible (DeepSeek)
[providers.deepseek]
type = "openai-compatible"
base_url = "https://api.deepseek.com"
api_key = { env = "DEEPSEEK_API_KEY" }
default_model = "deepseek-v4-flash"

# OpenAI-compatible (Aliyun/Qwen)
[providers.aliyun]
type = "openai-compatible"
base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1"
api_key = { env = "DASHSCOPE_API_KEY" }
default_model = "qwen3.6-plus"

Dual-Protocol Providers

Some providers support both OpenAI-compatible and Anthropic-compatible protocols:

# DeepSeek: OpenAI-compatible (default)
[providers.deepseek]
type = "openai-compatible"
base_url = "https://api.deepseek.com"
api_key = { env = "DEEPSEEK_API_KEY" }

# DeepSeek: Anthropic-compatible (alternative)
[providers.deepseek-anthropic]
type = "anthropic-compatible"
base_url = "https://api.deepseek.com/anthropic"
api_key = { env = "DEEPSEEK_API_KEY" }

# Moonshot: either protocol works
[providers.moonshot]
type = "openai-compatible"
base_url = "https://api.moonshot.cn/v1"
api_key = { env = "MOONSHOT_API_KEY" }
default_model = "kimi-k2.5"

API Key Resolution

API keys are resolved lazily at first use (not during startup), following this order per provider:

  1. api_key.env — named environment variable (e.g., DEEPSEEK_API_KEY)
  2. api_key.keychain — macOS Keychain service name (e.g., zen-deepseek-api-key)
  3. api_key_env — legacy env var field (deprecated)
  4. {PROVIDER}_API_KEY — auto-derived env var (e.g., OPENAI_API_KEY)
  5. Ollama/local providers — no auth required
# Using environment variable
api_key = { env = "DEEPSEEK_API_KEY" }

# Using macOS Keychain
api_key = { keychain = "zen-deepseek-api-key" }

# Direct env var name (auto-derived if not specified)
# ^ will try: DEEPSEEK_API_KEY automatically

Keychain Integration

On macOS, Zen integrates with the system Keychain for secure credential storage:

# Store an API key in Keychain
security add-generic-password -a "zen" -s "zen-openai-api-key" -w "sk-..."

# Configure provider to use Keychain
[providers.openai]
type = "openai"
api_key = { keychain = "zen-openai-api-key" }

If Keychain is unavailable (non-macOS, headless environment), Zen falls back to environment variables automatically.

Provider Fields Reference

FieldRequiredDescription
typeYesProtocol type (see table above)
base_urlFor some typesAPI endpoint URL (Ollama: http://127.0.0.1:11434)
api_keyFor cloud providersSecret reference (env var or keychain)
api_key_envNoLegacy env var name (deprecated)
default_modelRecommendedDefault model name for this provider
modelsNoPer-model catalog with parameters (see Model Catalog)
wire_apiNoWire protocol: "completions" (default) or "responses"

Next: Model Catalog & Parameters

Model Catalog & Parameters

Each provider can define a model catalog — a map of model names to their API identifiers, generation parameters, and named parameter variants.

Defining Models

When a models map is present under a provider, default_model selects a key in this map:

[providers.openai]
type = "openai"
api_key = { env = "OPENAI_API_KEY" }
default_model = "gpt-4o-mini"  # selects from [providers.openai.models]

    [providers.openai.models.gpt-4o]
    model = "gpt-4o"
    options = { temperature = 0.7, max_tokens = 4096 }

    [providers.openai.models.gpt-4o-mini]
    model = "gpt-4o-mini"
    options = { temperature = 0.3, max_tokens = 2048, reasoning_effort = "low" }

When models is absent, default_model is used directly as the API model name (backward compatible):

# Simple configuration — no model catalog
[providers.deepseek]
type = "openai-compatible"
base_url = "https://api.deepseek.com"
api_key = { env = "DEEPSEEK_API_KEY" }
default_model = "deepseek-v4-flash"  # used directly as API model name

Model Parameters

ParameterTypeDescriptionExample
temperaturefloat (0.0–2.0)Sampling temperature. Higher = more random0.7
max_tokensintegerMaximum tokens in the response4096
reasoning_effortstringReasoning depth: "low", "medium", "high""high"
top_pfloat (0.0–1.0)Nucleus sampling threshold0.9

Complete Example

[providers.anthropic]
type = "anthropic"
api_key = { env = "ANTHROPIC_API_KEY" }
default_model = "claude-sonnet-4-6"

    [providers.anthropic.models.claude-sonnet-4-6]
    model = "claude-sonnet-4-6"
    options = { temperature = 0.5, max_tokens = 8192 }

    [providers.anthropic.models.claude-haiku-4-5]
    model = "claude-haiku-4-5"
    options = { temperature = 0.3, max_tokens = 4096 }

[providers.ollama]
type = "ollama"
base_url = "http://127.0.0.1:11434"
default_model = "qwen3.6-35b-mlx"

    [providers.ollama.models.qwen3.6-35b-mlx]
    model = "qwen3.6:35b-mlx"
    options = { temperature = 0.6, max_tokens = 4096 }

Named Variants

Named variants allow the same model to be used with different parameters for different agents. This is useful when the same model needs different reasoning depths or creativity levels depending on the task.

[providers.anthropic.models.claude-sonnet-4-6]
model = "claude-sonnet-4-6"
options = { temperature = 0.5, max_tokens = 8192 }

    # Variant: creative
    [providers.anthropic.models.claude-sonnet-4-6.variants.creative]
    temperature = 0.9

    # Variant: precise
    [providers.anthropic.models.claude-sonnet-4-6.variants.precise]
    temperature = 0.1
    reasoning_effort = "high"

Agents reference variants via the variant field:

[agents.Prometheus]
provider = "anthropic"
model = "claude-sonnet-4-6"
variant = "precise"  # uses temperature=0.1, reasoning_effort="high"

Variant parameters merge into the base model options — they only override the fields specified.

Parameter Resolution Order

When an agent makes a call, parameters are resolved in this order:

  1. Model-level defaults from the model catalog entry
  2. Variant overrides (if specified)
  3. Agent-level overrides (if specified in [agents.*])

This means an agent can always override temperature or max_tokens at the agent config level, regardless of model defaults.


Next: Agent → Model Routing

Agent → Model Routing

Each agent can be assigned a specific provider and model, with a sequential fallback chain for reliability. Routing is defined in [agents.*] sections — both the knowledge-pipeline tasks (notion_extraction, contradiction_detection, synthesis, dispatch) and the 13 agent profiles (Sisyphus, Prometheus, Explore, …).

Basic Routing

[agents.Sisyphus]
provider = "anthropic"
model = "claude-sonnet-4-6"
fallbacks = [
    { provider = "openai", model = "gpt-4o" },
    { provider = "deepseek", model = "deepseek-v4-flash" }
]

The router tries providers in order: primary → first fallback → second fallback → ... → Mock (always available).

Agent Routing Fields

FieldRequiredDescription
providerYesPrimary provider name (must match a [providers.*] key)
modelNoModel override (falls back to provider's default_model)
fallbacksNoOrdered fallback chain if primary fails
sensitivityNoMax data sensitivity this agent may handle: "Public", "Private", or "Confidential" — Private/Confidential data is routed to local providers only

Agent identity traits (role, LLM preferences, clearance) are registry-hardcoded — they are not configurable per agent. Only the routing fields above are read from config.

Fallback Chain

Each fallback step can specify:

FieldDescription
providerProvider name for this fallback step
modelOverride model (optional, uses provider's default if omitted)
timeout_secsTimeout for this step (optional)
[agents.dispatch]
provider = "anthropic"
model = "claude-sonnet-4-6"
fallbacks = [
    { provider = "openai", model = "gpt-4o", timeout_secs = 30 },
    { provider = "ollama", model = "qwen3.6:35b-mlx" }
]

Sensitivity-Aware Routing

When sensitivity is set to "Private" or "Confidential", Zen enforces local-only routing for sensitive data:

  • Private/Confidential data is never sent to cloud providers
  • If no local LLM is available, the agent returns an error instead of falling back to cloud
# Knowledge pipeline — private data, local-first
[agents.notion_extraction]
provider = "ollama"
model = "qwen3.6:35b-mlx"
fallbacks = [{ provider = "deepseek", model = "deepseek-v4-flash" }]
sensitivity = "Private"

Complete Agent Configuration Examples

Orchestrator Tier (requires capable models)

[agents.Sisyphus]
provider = "anthropic"
model = "claude-sonnet-4-6"
fallbacks = [
    { provider = "openai", model = "gpt-4o" },
    { provider = "deepseek", model = "deepseek-v4-flash" }
]

Knowledge Pipeline (local-first)

[agents.notion_extraction]
provider = "ollama"
model = "qwen3.6:35b-mlx"
fallbacks = [
    { provider = "deepseek", model = "deepseek-v4-flash" },
    { provider = "openai", model = "gpt-4o-mini" }
]
sensitivity = "Private"

Fast Explorer (cost-optimized)

[agents.Explore]
provider = "anthropic"
model = "claude-haiku-4-5"
fallbacks = [{ provider = "openai", model = "gpt-4o-mini" }]

Next: Environment Variable Overrides

Environment Variable Overrides

Environment variables provide the highest-priority configuration layer. They're useful for temporary overrides, CI/CD environments, and sensitive values.

Global Overrides

# Default provider and model
export ZEN_DEFAULT_PROVIDER="deepseek"
export ZEN_DEFAULT_MODEL="deepseek-v4-flash"

Per-Task Overrides

Override knowledge-pipeline task routing without modifying any config files. Only these four distill tasks have env overrides:

export ZEN_AGENT_NOTION_EXTRACTION_PROVIDER="ollama"
export ZEN_AGENT_SYNTHESIS_MODEL="claude-sonnet-4-6"
export ZEN_AGENT_DISPATCH_PROVIDER="anthropic"
export ZEN_AGENT_CONTRADICTION_DETECTION_MODEL="qwen3.6:35b-mlx"

Supported task env var targets:

Env VarEffect
ZEN_AGENT_NOTION_EXTRACTION_PROVIDER / _MODELOverride notion extraction provider / model
ZEN_AGENT_CONTRADICTION_DETECTION_PROVIDER / _MODELOverride contradiction detection provider / model
ZEN_AGENT_SYNTHESIS_PROVIDER / _MODELOverride synthesis provider / model
ZEN_AGENT_DISPATCH_PROVIDER / _MODELOverride dispatch provider / model

Agent Engine Overrides

The [agentic.*] sections are env-overridable — handy for scripts and CI:

# Distill loop
export ZEN_LOOP_MAX_STEPS=20            # per-cycle distill budget (default 10)
export ZEN_LOOP_MAX_TOKENS=16000        # per-cycle token budget
export ZEN_LOOP_MAX_INGEST_BYTES=134217728  # skip files > 128 MiB

# Tool loop & delegation
export ZEN_TOOL_MAX_ROUNDS=12           # conversational tool rounds (default 8)
export ZEN_DELEGATE_MAX_DEPTH=2         # delegation depth (default 1)
export ZEN_DELEGATE_MAX_CONCURRENT=6    # fan-out width (default 4)

# Quality review
export ZEN_REVIEW_MAX_MOMUS_RETRIES=3
export ZEN_REVIEW_LLM_HIGH_BLAST=true

# Calibrated decision gates (absent = gate closed — pre-calibration behavior)
export ZEN_INTENT_L1_THRESHOLD=0.85
export ZEN_REVIEW_ESCALATE_THRESHOLD=0.9

# Retention sweep
export ZEN_RETENTION_DRY_RUN=true       # report-only, delete nothing

Full list by section:

SectionEnv Vars
[agentic.loop]ZEN_LOOP_ENABLED, _INTERVAL, _MERGE_THRESHOLD, _MAX_ATTEMPTS, _MIN_FREE_BYTES, _SKIP_EXTENSIONS, _HOST_STAGE_TIMEOUT_SECS, _MAX_STEPS, _MAX_TOKENS, _MAX_INGEST_BYTES, _COMMUNITY_RESOLUTION, _COMMUNITY_MIN_SIZE, _DISTILL_ALLOW_CLOUD, _TREE_INDEX_PAGES
[agentic.cache]ZEN_CACHE_BREAKPOINTS
[agentic.review]ZEN_REVIEW_MAX_MOMUS_RETRIES, _MAX_HERMES_REVISIONS, _LLM_HIGH_BLAST, _ESCALATE_THRESHOLD
[agentic.delegate]ZEN_DELEGATE_ENABLED, _TIMEOUT_SECS, _MAX_DEPTH, _MAX_CONCURRENT
[agentic.orchestrator]ZEN_ORCHESTRATOR_SURFACE (full | delegation-only)
[agentic.intent]ZEN_INTENT_SHADOW_EMBEDDING, ZEN_INTENT_L1_THRESHOLD
[agentic.classifiers]ZEN_CLASSIFIER_CORRECTION_THRESHOLD, ZEN_CLASSIFIER_CITATION_THRESHOLD
[agentic.audit]ZEN_AUDIT_DECISION_EXCERPT_CHARS (0 = off, ≤500)
[agentic.retention]ZEN_RETENTION_ENABLED, ZEN_RETENTION_DRY_RUN
[skills.auto_route]ZEN_SKILLS_AUTO_ROUTE
[channels.qqbot]ZEN_QQBOT_APP_ID, ZEN_QQBOT_CLIENT_SECRET

Cron & Scheduler Overrides

export ZEN_CRON_CONSOLIDATION_TIME="03:00"
export ZEN_CRON_TIMEZONE="America/New_York"          # IANA name; bad values fall back to Utc with a warning
export ZEN_CRON_SUBCONSCIOUS_INTERVAL_MINUTES=10
export ZEN_CRON_WISDOM_SYNTHESIS="0 0 2 * * 7"
export ZEN_TUI_SCHEDULER=false                       # disable all background learning outside `zen serve start`

Plugin & Embeddings Overrides

export ZEN_PLUGIN_BASE_PATH="/custom/plugin/path"
export ZEN_LEARNING_AUTO_RESEARCH="true"
export ZEN_LEARNING_INTERVAL="weekly"
export ZEN_FINANCE_BASE_CURRENCY="USD"

export ZEN_EMBEDDINGS_PROVIDER="local"
export ZEN_EMBEDDINGS_LOCAL_PROVIDER="fastembed"
export ZEN_EMBEDDINGS_MODEL="BGEM3"
export ZEN_EMBEDDINGS_CACHE_DIR="/custom/cache"

API Key Environment Variables

# Set API keys for providers
export OPENAI_API_KEY="sk-..."
export ANTHROPIC_API_KEY="sk-ant-..."
export DEEPSEEK_API_KEY="sk-..."
export DASHSCOPE_API_KEY="sk-..."
export GEMINI_API_KEY="..."
export COHERE_API_KEY="..."
export MISTRAL_API_KEY="..."
export GROQ_API_KEY="gsk_..."
export MOONSHOT_API_KEY="..."
export XAI_API_KEY="..."
export PERPLEXITY_API_KEY="..."

Priority Rules

  1. Explicit api_key.env in provider config takes precedence over auto-derived names
  2. Environment variables override config files
  3. Per-task env vars override that task's provider/model settings
  4. Set env vars in .env file (loaded automatically) or export them in your shell profile

Next: Configuration Examples

Configuration Examples

1. Local-Only Setup (Ollama Only)

default_provider = "ollama"
default_model = "qwen3.6:35b-mlx"

[providers.ollama]
type = "ollama"
base_url = "http://127.0.0.1:11434"
default_model = "qwen3.6:35b-mlx"

# No other providers needed

For complete offline operation with no cloud dependencies.

Best for: Privacy-critical environments, air-gapped setups, offline use.

2. Cloud-First with Local Fallback

Use cloud for quality, fall back to local when offline:

default_provider = "anthropic"
default_model = "claude-haiku-4-5"

[providers.anthropic]
type = "anthropic"
api_key = { env = "ANTHROPIC_API_KEY" }
default_model = "claude-haiku-4-5"

[providers.ollama]
type = "ollama"
base_url = "http://127.0.0.1:11434"
default_model = "qwen3.6:35b-mlx"

[agents.dispatch]
provider = "anthropic"
model = "claude-sonnet-4-6"
fallbacks = [{ provider = "ollama", model = "qwen3.6:35b-mlx" }]

Best for: Daily driver — cloud quality with offline resilience.

3. Multi-Cloud Hybrid Routing

Route different tasks to different cloud providers based on cost and capability:

default_provider = "deepseek"
default_model = "deepseek-v4-flash"

[providers.deepseek]
type = "openai-compatible"
base_url = "https://api.deepseek.com"
api_key = { env = "DEEPSEEK_API_KEY" }
default_model = "deepseek-v4-flash"

[providers.aliyun]
type = "openai-compatible"
base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1"
api_key = { env = "DASHSCOPE_API_KEY" }
default_model = "qwen3.6-plus"

[providers.groq]
type = "openai-compatible"
base_url = "https://api.groq.com/openai/v1"
api_key = { env = "GROQ_API_KEY" }
default_model = "llama-3.3-70b-versatile"

# Knowledge pipeline: local-first, cloud backup
[agents.notion_extraction]
provider = "ollama"
model = "qwen3.6:35b-mlx"
fallbacks = [
    { provider = "deepseek", model = "deepseek-v4-flash" }
]

# Orchestrator: capable cloud model with fallbacks
[agents.Sisyphus]
provider = "anthropic"
model = "claude-sonnet-4-6"
fallbacks = [
    { provider = "openai", model = "gpt-4o" },
    { provider = "deepseek", model = "deepseek-v4-flash" }
]

Best for: Cost optimization with multiple provider accounts.

4. Privacy-Preserving Setup

Local for sensitive data, cloud for public information:

default_provider = "ollama"
default_model = "qwen3.6:35b-mlx"

[providers.ollama]
type = "ollama"
base_url = "http://127.0.0.1:11434"

[providers.deepseek]
type = "openai-compatible"
base_url = "https://api.deepseek.com"
api_key = { env = "DEEPSEEK_API_KEY" }

# Private data stays local
[agents.Metis]
provider = "ollama"
sensitivity = "Private"

# Public research can use cloud
[agents.Explore]
provider = "deepseek"

Best for: Healthcare, legal, finance — any domain with data residency requirements.

5. Cost-Optimized Setup

Cheapest capable model for each task tier:

default_provider = "deepseek"
default_model = "deepseek-v4-flash"

[providers.deepseek]
type = "openai-compatible"
base_url = "https://api.deepseek.com"
api_key = { env = "DEEPSEEK_API_KEY" }

[providers.ollama]
type = "ollama"
base_url = "http://127.0.0.1:11434"

# Heavy reasoning: use local (free)
[agents.dispatch]
provider = "ollama"
model = "qwen3.6:35b-mlx"

# Light tasks: cheapest cloud API
[agents.Explore]
provider = "deepseek"
model = "deepseek-v4-flash"

# Synthesis: use best model sparingly
[agents.synthesis]
provider = "deepseek"
model = "deepseek-v4-flash"
fallbacks = [{ provider = "ollama", model = "qwen3.6:35b-mlx" }]

Best for: Budget-conscious setups, hobbyist use, development.

6. Complete Production Config

default_provider = "anthropic"
default_model = "claude-haiku-4-5"

[providers.ollama]
type = "ollama"
base_url = "http://127.0.0.1:11434"
default_model = "qwen3.6:35b-mlx"

[providers.anthropic]
type = "anthropic"
api_key = { env = "ANTHROPIC_API_KEY" }
default_model = "claude-haiku-4-5"

[providers.openai]
type = "openai"
api_key = { env = "OPENAI_API_KEY" }
default_model = "gpt-4o-mini"

[providers.deepseek]
type = "openai-compatible"
base_url = "https://api.deepseek.com"
api_key = { env = "DEEPSEEK_API_KEY" }
default_model = "deepseek-v4-flash"

# Orchestrator
[agents.Sisyphus]
provider = "anthropic"
model = "claude-sonnet-4-6"
fallbacks = [
    { provider = "openai", model = "gpt-4o" },
    { provider = "deepseek", model = "deepseek-v4-flash" }
]

# Planner
[agents.Prometheus]
provider = "anthropic"
model = "claude-sonnet-4-6"
fallbacks = [{ provider = "openai", model = "gpt-4o" }]

# Knowledge pipeline
[agents.notion_extraction]
provider = "ollama"
model = "qwen3.6:35b-mlx"
fallbacks = [
    { provider = "deepseek", model = "deepseek-v4-flash" },
    { provider = "openai", model = "gpt-4o-mini" }
]

# Fast exploration
[agents.Explore]
provider = "anthropic"
model = "claude-haiku-4-5"
fallbacks = [{ provider = "openai", model = "gpt-4o-mini" }]

# Privacy-sensitive analysis
[agents.Metis]
provider = "deepseek"
model = "deepseek-v4-flash"
fallbacks = [{ provider = "ollama", model = "qwen3.6:35b-mlx" }]
sensitivity = "Private"

# Worker agents
[agents.Hephaestus]
provider = "anthropic"
model = "claude-sonnet-4-6"
fallbacks = [
    { provider = "openai", model = "gpt-4o" },
    { provider = "deepseek", model = "deepseek-v4-flash" }
]

[agents.Atlas]
provider = "ollama"
model = "qwen3.6:35b-mlx"
fallbacks = [{ provider = "deepseek", model = "deepseek-v4-flash" }]

[agents.Junior]
provider = "ollama"
model = "qwen3.6:35b-mlx"
fallbacks = []

7. Self-Learning Loop Tuning

The background distill loop and its guards are tuned under [agentic.*]. All keys are optional — unset keys use built-in defaults:

# Distill loop: how fast the inbox drains
[agentic.loop]
interval = "0 */5 * * * *"       # every 5 minutes (cron, [cron] timezone applies)
max_steps = 10                   # per-cycle budget, clamp 1..=100 (env ZEN_LOOP_MAX_STEPS)
max_tokens = 8000                # per-cycle LLM token budget
max_ingest_bytes = 67108864      # skip+quarantine files > 64 MiB
reverify_older_than_days = 7     # re-verify hypotheses older than this

# LLM distill enrichment (T046): names a PROVIDER (e.g. "ollama") whose
# configured default model extracts notions per note and composes merge
# sections. Unset = deterministic heuristic path only (default). Any LLM
# failure is fail-open.
# merge_llm_model = "ollama"
# distill_allow_cloud = false    # must be true to let a cloud provider serve distill calls
# tree_index_pages = 20          # notes longer than this many 3000-char pages are
                                 # distilled one call per heading section (clamp 5..=200)

# Prompt-cache breakpoints (Anthropic routing only)
[agentic.cache]
breakpoints = true               # cache the stable system prefix (env ZEN_CACHE_BREAKPOINTS)

# Agent tool loop: rounds per conversational turn
[agentic.tool_loop]
max_rounds = 8                   # clamp 1..=16 (env ZEN_TOOL_MAX_ROUNDS)

# Sub-agent delegation from the orchestrator
[agentic.delegate]
enabled = true
max_depth = 1                    # how deep delegation can nest, clamp 1..=3
max_concurrent = 4               # parallel fan-out, clamp 1..=8
timeout_secs = 300               # clamp 30..=1800

# Daily retention sweep (log/archive rotation + age deletes)
[agentic.retention]
enabled = true
dry_run = false                  # true = report only, delete nothing

Best for: Tuning throughput vs cost, or taming the loop on low-power machines.

8. QQ Bot Channel

Enable the QQ official-bot channel so allowlisted chats converse with your persistent agent sessions. Requires zen serve start (the channel runs inside the gateway daemon):

[channels.qqbot]
app_id = "your-qq-app-id"              # or env ZEN_QQBOT_APP_ID
client_secret = "your-qq-secret"       # or env ZEN_QQBOT_CLIENT_SECRET
allowed_users = [                      # deny-by-default; group member openids
    "ABCDEF1234567890",                # and C2C user ids may be mixed
]
outbox_drain_interval_secs = 300       # morning-brief active-send tick, clamp 60..=3600

In chat, /new resets the session and /status renders gateway health. zen doctor fails the outbox probe with an actionable message when briefs are staged but the channel is unconfigured.

Best for: Talking to your knowledge base from QQ, receiving the morning brief.

9. Cost Tracking & Budget Cap

Per-provider token costs (USD per million tokens) make the [cron] budget cap real, and zen discover report surfaces actual spend:

[cron]
llm_cost_cap_usd = 10.0          # monthly cap across scheduled workers (default 10.0)

[providers.deepseek]
type = "openai-compatible"
base_url = "https://api.deepseek.com"
api_key = { env = "DEEPSEEK_API_KEY" }
default_model = "deepseek-v4-flash"
input_cost_per_million = 0.27
output_cost_per_million = 1.10

[providers.anthropic]
type = "anthropic"
api_key = { env = "ANTHROPIC_API_KEY" }
default_model = "claude-haiku-4-5"
input_cost_per_million = 0.80
output_cost_per_million = 4.00

Local (Ollama) usage is structurally free — cost 0.0. Check actual spend with zen discover report (worker costs persist under ~/.zen/logs/worker-costs.json).

Best for: Keeping scheduled background learning inside a predictable budget.


Next: Introduction — back to guide start

System Architecture

Zen is a Rust CLI productivity suite with agentic workspace architecture. It follows a binary/library split with 12 workspace crates.

High-Level Architecture

┌─────────────────────────────────────────────┐
│  zen (binary, 13 lines)                     │
│  loads .env, calls zen_cli::shell()         │
└──────────────────┬──────────────────────────┘
                   │
┌──────────────────▼──────────────────────────┐
│  zen-cli (library)                           │
│  clap Parser (23 commands), TUI (ratatui)   │
│  command dispatcher                          │
└──┬────┬────┬────┬────┬────┬────┬────┬───────┘
   │    │    │    │    │    │    │    │
   ▼    ▼    ▼    ▼    ▼    ▼    ▼    ▼
┌────┬────┬────┬────┬────┬────┬────┬──────┐
│zen │zen │zen │zen │zen │zen │zen │zen   │
│core│repo│vault│agents│mem │prov│auth│...  │
└────┴────┴────┴────┴────┴────┴────┴──────┘

Crates Overview

CrateRoleKey Modules
zenBinary entry (13-line main.rs).env loading, dispatches to zen_cli::shell()
zen-cliCLI library23 commands, TUI (ratatui), clap derive dispatch
zen-coreCore infrastructure4-layer global-only config, error taxonomy, path scoping, constants, secrets
zen-serviceBusiness logicStarter/wps/cleanup services
zen-repoData layersqlx + rusqlite dual API, FTS5, vec0, graph schema
zen-vaultKnowledge servicesNote, Wiki, 5-tier search, consolidation, lint, ingest
zen-agentsAgent system13 agents, 4 tiers, plan-DAG executor, QualityPipeline, registry, scheduler workers
zen-providerLLM routing13 providers, 3 protocol types, DefaultRouter, auth resolution
zen-memoryIdentity contextSOUL.md, MEMORY.md loading and management
zen-authCredential managementKeychain integration, SecretRef resolution
zen-pluginExtension systemWASM sandbox (wasmtime), MCP client/server, agent tools
zen-gatewaySole-owner daemonUDS JSON-RPC 2.0, hosted agent sessions, loopback HTTP carrier, MCP stdio, QQ bot channel

Key Architectural Patterns

Binary/Library Split

The zen binary (13 lines in crates/zen/src/main.rs) loads .env, calls zen_core::config::load_config(), then delegates to zen_cli::shell().await. All logic lives in library crates.

4-Layer Configuration (global-only)

Rust built-in defaults → embedded config.toml → ~/.zen/config.toml → ZEN_* env vars

Each layer merges into the previous one — higher layers override only the keys they set. Configuration is global-only: the .zen/ directory marks project context but is not a config layer.

4-Tier Agent Architecture

Orchestrator (L0) → Planner (L1) → Specialist (L2) → Worker (L3)
  • Orchestrator: Session coordination, intent routing (AgentOrchestrator, Sisyphus)
  • Planner: Task decomposition, plan-DAG execution (Prometheus)
  • Specialist: Domain expertise (search, consolidate, research)
  • Worker: Execution, tool calling (AgentExecutor)

5-Tier Search Pipeline

ripgrep → FTS5 → vector embeddings → entity graph → LLM

Each tier adds depth: keyword search first, semantic when needed.

13 Built-In Agents

AgentTierRole
SisyphusL0Lead orchestrator
PrometheusL1Task planner
MetisL1Plan correctness review
MomusL1Plan quality review
ZeusL1Escalation handler
OracleL2Architecture consultation
ExploreL2Codebase exploration
LibrarianL2External reference search
ArgusL2Monitoring & observation
HephaestusL3Tool execution
AtlasL3Knowledge pipeline
JuniorL3General task execution
HermesL3Safety & audit

Data Flow: Note Creation to Wiki

note dropped into vault/inbox/ → ZenScheduler (zen_loop worker, every 5 min)
    → zen-vault (DistillationPipeline: extract → normalize → compile → merge → archive)
    → zen-vault ingest (pdf/office/sidecar formats convert to Markdown at intake)
    → zen-provider (entity extraction)
    → zen-repo (graph entities + FTS5 + embeddings)
    → zen-vault (WikiPage generation, AtomicWikiWriter)

search (TUI chat / agent tools) → zen-vault (SearchService)
    → tier 1: ripgrep
    → tier 2: FTS5 (notes_fts)
    → tier 3: vec0 embeddings
    → tier 4: entity graph (+ PPR seeded ranking)
    → tier 5: LLM semantic reranking

Learn more: AGENTS.md for the full project knowledge base.

Development

Prerequisites

  • Rust 1.80+ (edition 2024)
  • macOS (primary development platform)

Build

# Build all crates
cargo build

# Build release binary
bin/build

Test

# Run all tests
cargo test

# Run with nextest (recommended for env-isolated tests)
cargo nextest run

# Run specific crate tests
cargo test -p zen-core
cargo test -p zen-provider
cargo test -p zen-agents

Note: Some tests (particularly path tests) require cargo nextest for process isolation because they modify environment variables.

Lint & Format

# Run linter
bin/lint

# Or individually:
cargo clippy -- -D warnings
cargo fmt --all --check

# Format code
cargo fmt --all

Project Structure

zenspace/
├── crates/               # 12 Rust workspace crates
│   ├── zen/              # Binary entry (13-line main.rs)
│   ├── zen-cli/          # CLI library + TUI
│   ├── zen-core/         # Core infrastructure (13 modules)
│   ├── zen-service/      # Business logic
│   ├── zen-repo/         # Data layer (sqlx + rusqlite)
│   ├── zen-vault/        # Knowledge services
│   ├── zen-agents/       # Agent system
│   ├── zen-provider/     # LLM provider routing
│   ├── zen-memory/       # Identity context
│   ├── zen-auth/         # Credential management
│   ├── zen-plugin/       # WASM sandbox + MCP
│   └── zen-gateway/      # HTTP daemon
├── config/               # Embedded config.toml
├── templates/            # Tera templates
├── docs/                 # Documentation
│   ├── src/              # User guide (mdBook source)
│   │   ├── introduction.md
│   │   ├── installation.md
│   │   ├── quickstart.md
│   │   └── ...
│   ├── theme/            # mdBook custom theme
│   └── book/             # Generated HTML output
└── bin/                  # Build/test/lint/release scripts

Architecture specifications: deep architecture/spec docs live in AGENTS.md (the dev-facing knowledge base). The former docs/specs/ directory is no longer present on disk.

Release

# Automated release
bin/release patch    # 0.1.0 → 0.1.1
bin/release minor    # 0.1.0 → 0.2.0
bin/release major    # 0.1.0 → 1.0.0

# Manual release
echo "0.1.2" > VERSION
git commit -am "release: v0.1.2"
git tag v0.1.2
git push origin main --tags

Code Conventions

  • Imports: use crate:: for internal, use zen_*:: for cross-crate
  • Errors: thiserror for libraries, anyhow for app-level
  • CLI commands: Each command in src/cmd/{name}_command.rs with pub fn execute_command()
  • Config: Embedded config/config.toml + user ~/.zen/config.toml
  • Tests: Integration tests only (no inline #[cfg(test)] in most crates)

Design Principles

See AGENTS.md for the full project constitution:

  1. CLI-First — Every feature via CLI subcommands
  2. Robust Error Handling — thiserror for types, anyhow for propagation
  3. Observability — Structured logging via tracing
  4. Configuration — .env via dotenvy, 4-layer global-only config inheritance
  5. Template-Driven — Embedded templates via include_dir
  6. Code Quality — Zero warnings, unsafe blocks justified
  7. Single Responsibility — 12 crates with clear boundaries
  8. Testing — Unit + integration tests required
  9. UX Consistency — JSON/human-readable dual output
  10. Performance — <500ms cold start, <50MB footprint
  11. Design-First & Reuse — Reuse frameworks, don't reinvent