Skip to content

Architecture

System-level constraints and long-lived design principles.

User-facing product terms (Chinese in i18n/glossary.md):

RoleEnglishChineseMeaning
Long-running process / connect targetHabitat栖息地One process hosts multiple digital lives (agent subjects) and human assets (user); connect / token / restart target
External connectors (class)Portal入口Class of ways into Habitat; four forms: application / browser / mcp / cli
Portal form入口形态Realization kind of a Portal
Application-form PortalShell / 应用形态Form application — windowed SPA(desktop / mobile / web)。Not app frame
Browser-form Portal浏览器形态入口Form browser — browser extension(MV3);src/portal/extensionnot Web Shell
MCP-form PortalMCPMCP 形态入口Form mcp — Habitat /mcpmcp-server)。Inbound mcp-client is not a Portal
CLI-form PortalCLICLI 形态入口Form clianima CLI;src/portal/cli
Remote-tool registrantOutpost前哨Unreachable local app that remote_tools.attach (Portal-embedded companion or standalone tool); not a Portal
ShellShellApplication-form Portal(desktop / mobile / web)。Not Habitat; not app frame; not browser Portal
app frameapp frame应用布局SPA chrome in src/client/app-frame (AppFrame); viewport-driven; orthogonal to Shell
Admin / inspect UI (legacy Habitat)Habitat (UI)栖息地Area under /habitat/*; “Open Habitat” vs “Connect to Habitat”
Admin home pageDashboard仪表盘/habitat/dashboard only; other Habitat routes keep their own labels
Message bridgesGatewayGatewayDiscord / WeChat — not a Portal
Protocol / code identifiers协议/代码标识/rpc/v1HabitatRPC/1.0habitat_*habitat_runtime_configdev:habitat

Verbs: connect to Habitat (URL + token); open Habitat (admin UI); reach via a Portal (Shell / browser extension / MCP / CLI).

Code layout: src/portal/{app,extension,cli};MCP-form implementation stays in src/host/capabilities/mcp-server. See docs/modules/portal.md.

Estate Body (VM / OS / network under the four-layer model) is the cognitive “what I run on” for a subject — not the Habitat process name.

User copy says Habitat. Storage/RPC identifiers use habitat_* / HabitatRPC/1.0 / /rpc/v1.

LayerStorageWho reads/writes
Bootstrap~/.anima/config.yaml (database, http, redis)platform/boot only; install/ops edit YAML
RuntimePostgreSQL habitat_runtime_configEngine, tools, Shell Habitat settings, Habitat UI config.*

Legacy hub_* / console protocol aliases and dual-write keys have been removed; use Habitat identifiers only.

  • The memory system may be layered internally, but the LLM sees a single entry point
  • Memory orchestration is built into the runtime; the LLM does not control memory pipelines
  • Credential management is a first-class system concern
  • Habitat runtime configuration (LLM, compression, integrations) is persisted in PostgreSQL (habitat_runtime_config); ~/.anima/config.yaml holds bootstrap only (database, http, redis) for cold start — not editable via Shell or Habitat UI API
  • Habitat may start without LLM configured; first-time setup happens in Shell Settings → Habitat (persist to PG). Saving runtime config hot-applies in memory (no Habitat restart). Missing llm must not block cold start.
  • Habitat hosts browser /web/* whenever Web dist is present (no config switch; run just pack web for source deploy). Source just dev habitat skips hosting so Vite serves the UI.
  • Asset management is a first-class system concern

Bootstrap and runtime are not merged into a single config object (no AnimaConfig / animaConfigSchema supersets). Types: bootstrapConfigSchema vs runtimeConfigSchema / Config.data: RuntimeConfig. CLI cold paths connect via internal withPlatformDb and receive runtime only. Legacy runtime keys in config.yaml are ignored (optional startup warn).

KindSections (examples)After UI save
Livecompression, memory, fts, cjk, clarify, browser, firecrawl, models, tts, auto_llm, gateway tool_displayConsumers read Config.data each use; snapshot update is enough
Transferredllm, i18n, embedding, mcp_servers, acp_agents, discord / weixin / gateway platforms, worldsSnapshot update plus section apply (re-init registries / reconnect / re-bind)
Bootstrapdatabase, http, redisEdit YAML; process restart required
Hard / rareeventbus (queue already built)Prefer process restart if changing key_prefix etc.
  • The system prompt is part of architecture, not ad-hoc string concatenation

A digital life is structured from the inside out. Each layer answers a different core question:

┌───────────────────────────────────────────────┐
│ ① Consciousness │
│ "What am I aware of right now?" │
│ The LLM runtime stream — the innermost now. │
│ Not persisted; flows and fades. │
├───────────────────────────────────────────────┤
│ ② Self │
│ "Who am I?" │
│ └── existence_anchor (nearly immutable) │
│ └── self_model (updatable) │
│ └── personality_baseline (semi-stable) │
│ └── direction │
│ └── metacognition │
│ └── autobiography_summary (append-only) │
│ See [`self-layer.md`](../cognition/self-layer.md) │
├───────────────────────────────────────────────┤
│ ③ Memory │
│ "What do I know / remember?" │
│ └── Semantic (incl. `procedural` type) │
│ └── Episodic │
│ └── Limbic / Imprint │
│ See [`memory.md`](../cognition/memory.md) │
├───────────────────────────────────────────────┤
│ ④ Estate │
│ "What do I have / rely on?" │
│ ├── Body: VM / OS / network / toolchains │
│ │ (Estate cognitive body — not Habitat) │
│ ├── Internal assets: notes, projects, code │
│ └── External assets: email, accounts, creds│
│ Credentials: see "Credential System" below │
└───────────────────────────────────────────────┘
  • Top-down dependency: Consciousness content settles into Self; Self decides what enters Memory; Memory and operational needs drive Estate requirements.
  • Self and Consciousness: Consciousness is flowing awareness; Self is the settled “I” distilled from it.
  • Self and Memory: Self answers “who am I”; Memory answers “what do I know”. They are peers with different natures.
  • Estate is outermost — not “who I am” but “what I have and what I run on”. Body and assets meet here as extensions of my boundary.

Structured business data (tasks, notes, email accounts/messages, future memory migrations) converges on a single entities PostgreSQL table with component tags (task_list, task_item, email_account, …). Self layer self_blocks stays physically separate. See entity-model.md(remove / deleteComponent / deleteEntity 软删与回收站;Shell Entity 模块见 ../modules/entity.md)。

Shell UI /tasks and /email are primary module entries (entity-backed); legacy Habitat email route removed.

Entity deep links / overlay / clipboard use Anima URI (anima:{id}?component=…&present=…). Structured persistence still uses numeric entity ids. See anima-uri.md.

Repository layout (Phase 1 — host/client)

Section titled “Repository layout (Phase 1 — host/client)”

Target layout is feature modules under src/features/<slug>/ (UI + protocol + Habitat adapter + domain + plugin.ts). Habitat 管理台 uses the same module shape as chat/task — not a separate admin-* stack. src/features/companion/ is legacy naming; do not add new products there.

End state: Habitat RPC per feature; business methods use POST|WS /rpc/v1 (same envelope). Public health/TLS probes and binary methods (e.g. tts.synthesize) are Habitat RPC REST with auth: optional or Bearer as declared in registry.

Authoritative spec: repository-topology.md.

Host stack: src/host/{kernel,core,engine,capabilities,platform}。Client: src/client/{portal-sdk,app-frame}。Design system: src/ui-kit/(与 shared/ 并列)。Portal Shell: src/portal/app/{tauri,web}

UI/UX design system (dimensions, visual foundations, components, interaction patterns) → docs/ui/. Agent hard bans / API → .agent/rules/ui-dimensions.md, .agent/rules/frontend-ui.md.

LayerPlatform-native?LocationData path
Shell(壳子维)Yessrc/portal/app/tauri, companion, Habitat bindingTauri IPC / commands
app frameLayout follows viewport; settings chrome follows layout bandsrc/client/app-frameAppFrameHabitat RPC(Feature RPC)
Habitat UIShell embed(ordinary feature)src/features/habitat(UI + plugin.habitat.rpcHabitat RPC(WS + HTTP POST /rpc/v1
Companion hostOverlay WebView-host(first-party)src/features/companion/companion(spa attach;thin shell IPC/FS)Habitat RPC + remote_tools.attach

Navigation and main layout must use useLayoutMode() / viewport breakpoints (layout dimension). Do not lock app layout with getShellKind(). Interaction (context menu / long-press / Enter-to-send) uses portal-sdk interaction APIs. Visual, component, and pattern norms all adapt through the same three dimensions.

Boundaries: app-frame and src/features/*/ui reach Habitat via portal-sdk + Feature RPC. Remote tool registration (remote_tools.attach + tool.*) is only for local apps Habitat cannot dial (today: companion first-party overlay / embedded-overlay; shell provides window/IPC/FS only; no Node sidecar). Product surfaces (Chat, etc.) do not attach. Dialable peers use MCP. See .agent/rules/frontend-features.md, docs/ops/habitat-rpc.md.

Habitat sidebar is grouped (not flat storage tables). Map new features onto these user-visible concepts:

GroupCognitive layerRoutes (representative)
RuntimeEstate + opsdashboard, config, cron
MemoryMemorymemory browse sub-routes, sleep, auto-llm-runs
SelfSelfself-layer, system-prompt
EstateEstatesubjects, worlds
CapabilitiesEstate (tools)tools, commands, mcp, acp, remote-tool instances

FTS index maintenance is under Memory (not top-level). Do not add new flat nav items without mapping to a group above.

The four-layer model draws on cognitive psychology and the Hindsight four-network memory architecture, with two fundamental extensions: limbic (emotional) memory and Estate (assets as first-class citizens), plus Self split out from Memory.

Where a digital life exists, how it exists, and what it may do — governed by two independent but cooperating subsystems.

Question: what kind of moment is this?

Scene awareness is soft — it adjusts tone, distance, memory recall bias, and proactivity. It is not a permission system; it modulates presence.

Example dimensions (non-exhaustive):

  • Topic: emotional / career / tech / philosophy / history / literature / daily life
  • Activity: role-play / games / creation / programming / reading
  • Atmosphere: relaxed / focused / late night / intimate / urgent

Operation: Runs continuously without explicit switch commands. Inferred from dialogue, time, frequency, etc. See time-perception.md.

Question: what is my host / runtime quiet state?

Distinct from scene awareness: banded host and process markers (disk, RSS, deps, MCP/ACP) live as a session-static system-prompt copy, with event-level Inbox notifications on change. See environment-awareness.md.

Question: what tools and data can I use right now?

The capability mask is hard — it binds tool sets, data scope, and credential permissions. The same digital life may wear different masks per conversation or task to prevent permission leaks and tool pollution.

Example masks:

  • Developer: terminal, code read/write, ACP Cursor
  • Maintainer: FreeAnima config, deployment, database
  • Creator: files, notes, media generation
  • Research: web search, paper retrieval
  • Role-play: dialogue context only, no external tools
  • Default: basic chat + limited lookup

Operation: Switches at conversation boundaries, explicit commands, or scene-awareness triggers. Each mask is a declared tool/data scope, not a separate identity.

Scene awareness (soft tuning)
│ tone, distance, recall bias
Capability mask (hard constraints)
│ tools, permissions, data scope
Agent behavior
  • Scene awareness infers “what we are doing” → may suggest mask switches and presence adjustments
  • Capability mask constrains “what I can do” → prevents cross-scene tool misuse
  • Both converge in final behavior but evolve independently

For design drafts, open a GitHub Issue (no design-doc directory in docs).

Cognitive typeDescription
EpisodicConversation archive; full history retained
SemanticCross-session facts, preferences, experiences; procedural type for “how-to” knowledge
LimbicEmotional anchors and imprints — “what was felt”
AutobiographicalMeaning of important experiences; recalled on demand

Pipeline: nightly sleep-cycle pipeline (in-process Bun.cron builtin-sleep-cycle) extracts and maintains memory; memory_recall retrieves on demand during chat. Details: memory.md, sleep.md.

  • Vault (ECS vault_item in User + Agent libraries) is the authoritative secret store; legacy ~/.password-store (pass) is not deleted on disk but is no longer read at runtime
  • The LLM never sees secret values — only vault item metadata and config references
  • Bootstrap config.yaml (cold start, before PG): plaintext or env("KEY") only — not vault(). Runtime PG config: vault("item_id", "field") and env("KEY"); Shell /vault for management
  • Secret values are not written to conversation archives or logs

See ops/security.md.

Production (standalone install CLI): anima service (systemd —user). Auto-restarts after crashes; only systemctl stop stops the service. Source-tree anima does not register service — use just dev habitat for local Habitat.

  • Habitat / service: long-running — Habitat HTTP (/rpc/v1), Discord / WeChat Gateway, cron
  • UI: src/portal/app/tauri + web/dist-* bundled SPA (Chat + Habitat); Habitat does not host /habitat
Terminal window
# standalone install
anima service start # default: systemd --user (does not auto-build Web)
anima service start --foreground # foreground (logs to stdout; systemd unit uses this)
anima service status
# monorepo / worktree
just dev # Habitat (≥10000) + Vite Web (≥5000)
just dev habitat # Habitat foreground (default random ≥10000; not 2658)
just dev web # browser shell Vite HMR from :5000 (set FREEANIMA_URL)
just pack web # source deploy / Habitat /web: build dist before start

Tools are registered in three layers but exposed to the LLM as one flat tool list. The LLM cares about names and parameters, not origin.

LLM view — flat tool list:
file_read_file(path) ← local
file_write_file(path, content) ← local
code_execute(code) ← local
query_database(sql) ← MCP server
send_email(to, subject) ← MCP server
acp_cursor(goal, context) ← ACP agent
  • Execute inside the FreeAnima process; lowest latency
  • Registered automatically at service startup

Layer 2: MCP Tools (Model Context Protocol)

Section titled “Layer 2: MCP Tools (Model Context Protocol)”
  • Connect to external MCP servers (separate processes)
  • Each server may register many fine-grained tools (single function calls)
  • Configure under Habitat runtime mcp_servers (PG habitat_runtime_config); manage in Habitat UI /habitat/mcp (config + start/stop + tools). Shell Settings no longer edits this section.
mcp_servers:
database:
command: npx
args: ["@modelcontextprotocol/server-postgres", "postgresql://..."]
transport: stdio
env:
PGOPTIONS: "-c statement_timeout=5s"
remote_habitat:
transport: http # Streamable HTTP — use for FreeAnima Habitat /mcp
url: http://127.0.0.1:2658/mcp
headers:
Authorization: "Bearer fa_at_…"

Layer 3: ACP Tools (Agent Client Protocol)

Section titled “Layer 3: ACP Tools (Agent Client Protocol)”
  • Each external agent instance registers as one task-level tool: acp_{name}(goal, context)
  • For full task delegation (coding, analysis, booking, etc.); seconds to minutes latency
  • Configure under Habitat runtime acp_agents (PG habitat_runtime_config); edit in Shell Settings → Habitat 服务 → 服务配置
# habitat_runtime_config fragment (not config.yaml)
acp_agents:
cursor:
command: ~/.local/bin/agent
args: ["--force", "acp"]
name: cursor
description: "Delegate coding, refactoring, and code review"
DimensionLocalMCPACP
Runs inProcessExternal serverExternal agent
GranularityFunctionFunctionFull task
LatencyMillisecondsMilliseconds–secSec–minutes
ConfigBuilt-inmcp_serversacp_agents

Layers can be mixed; the LLM chooses order; FreeAnima registers and routes.

Axis: whether the execution has a user turn during the run (not who triggered it).

KindUser turnPG persistenceProcess traceSleep pipeline
Conversationyessessions + messages (code still says session)message archiveparticipates (light sleep, dream input)
AutoLlmRunnoauto_llm_runs via runAutoLlm()audit row, TTLexcluded
Delegation (ACP)no (external)parent conversation + acp_tasksoptionalresult via parent conversation
Script cronnocron_log onlystdout fileexcluded

AutoLlmRun covers: cron agent branch, sleep LLM stages, future internal subagents. Tool context uses contextKind: auto_llm so memory_remember does not attach source_conversations. Cron no_agent shell scripts are not AutoLlmRun.

Question: should this conversation keep working toward a stated outcome?

Session Goal is an in-process autonomous loop at the Estate / orchestration layer — distinct from ACP async delegation:

DimensionSession GoalACP
ScopeSingle conversationExternal agent task
Trigger/goal slash + post-turn judgeTool call + callback turn
Persistenceconversations.goal JSONBconversations.acp_tasks
ContinuationSame SSE stream, turn budgetIndependent message after task update

Judge uses optional llm.profiles.goal_judge; on judge call/parse failure the goal is paused (warn logged + status line in chat). User messages preempt the loop; /goal pause / /goal resume control auto-continue without clearing state. See goal.md.

Client UI(web/dist SSOT + 原生壳打包)

Section titled “Client UI(web/dist SSOT + 原生壳打包)”

Portal Shell 运行时Tauri(Rust 主进程 + 系统 WebView;桌面与 Android 统一壳层)。壳规则:.agent/rules/tauri-shell.md禁止为 companion 再打 Node sidecar;remote_tools.attach 在第一方伴侣浮层(见 Desktop companion)。

UI 源码产物src/portal/app/web/distbase: /web/)。

客户端UI 加载更新方式
浏览器 / PWAHabitat 托管 /web/*(有 dist 时始终托管)Service Worker 提示新版本后手动重载(不自动刷新);跟 GitHub 包通道
Desktop安装包内 ui/webprepare-tauri-ui);调试可用 Tauri dev按 bake channel 查 GitHub(release=stable latest + semver;canary=tag canary + commit);用户确认后 NSIS 覆盖;可切换 releasecanary;About 可选公共 gh-proxy(默认直连)
Mobile APK安装包内 ui/web(本地同源);Habitat 仅 API同上轨语义;有 APK asset 才提示;确认后系统安装器覆盖;可切换轨;同上代理选择
Standalone嵌入 Web UI 的单文件 animaanima upgrade / --channel release|canary / --proxy …dev / 源码安装不可换轨;curl 安装脚本可用 PROXY=…

壳层保留原生能力(Tauri commands / prefs / 通知等)。无壳内 UI OTA:原生端不从 Habitat 热替换 SPA。允许用户确认后的安装包级覆盖(Desktop 安装包 / Mobile APK / Standalone anima upgrade → 独立前缀如 ~/.anima/standalone)。分发轨 SSOT 为安装包 bake 的 build-meta.channelrelease / canary / dev)。Habitat 配置统一走 settings「连接」(/settings);无独立 bootstrap Habitat 页。

ConceptWhatCode
ShellPortal host runtime(browser / Tauri;形态 web / desktop / mobile)src/portal/app/*portal-sdkgetShellKind / ShellApi / buildTarget
app frameSPA chrome:模块左栏 Rail / 底栏 Tabs、设置页 chromesrc/client/app-frameAppFrame);跟视口,由壳类型锁定

三维度模型(壳子 / 布局 / 交互)

Section titled “三维度模型(壳子 / 布局 / 交互)”
维度驱动职责
壳子getShellKind()web/tauri)+ getShellBuildTarget()存储、IPC、Habitat 连接、settings 内容字段、原生能力
布局仅视口断点(壳不锁底栏/左栏)compact / expanded;列表 drawer / 并列 / 三栏;settings chrome(tabs vs 侧栏)
交互primaryInput(touch / pointer)长按 vs 右键、Enter 发送等

手机端通常只有窄档,但 手机端 ≠ 窄布局;Portal / 浏览器窗口可以是窄或宽任意档。标准 → docs/ui/dimensions.md(Agent API → .agent/rules/ui-dimensions.md)。

档位视口布局粗档Nav IA页内
< 768px(Tailwind md)compact底栏 + Moredrawer
768–1027pxexpanded左侧 Rail两栏(清单 drawer)
≥ 1028pxexpanded左侧 Rail三栏并列

resolveLayoutMode():窄 → compact,中宽 → expanded(URL / config.json 可覆盖)。detectSettingsChromePlatform() 跟布局粗档(设置页 chrome);settings 字段差异由壳子维 resolveShellBindings() / getShellKind() / getShellBuildTarget() 决定。

客户端UI 加载壳发版
浏览器 / PWAHabitat /web/*随 Habitat / anima upgrade
Desktop安装包内本地 /web/*(默认)Tauri 安装包
Mobile APK安装包内本地 /web/*Tauri Android
ModuleConnectionNotes
ChatHabitat RPC /rpc/v1 (shared WS, no remote-tool attach)/web/chat
HabitatHabitat RPC /rpc/v1 (WS + HTTP POST, same envelope)/web/habitat/dashboard

/web/config.json 提供 habitat_urlhabitat_ws_urlui_versionmin_shell_version(浏览器/PWA 与壳调试用;原生壳 UI 版本随安装包)。

  • EventBus: async notification transport (Redis queue); production code currently emits topics such as session:updated with no registered handlers — ACP callbacks use direct onSessionUpdated instead. Not used for sleep orchestration.
  • Pipeline Runner: explicit DAG for background cycles (sleep-cycle: light → deep → cross-domain maintenance steps). State in ~/.anima/runtime/pipeline_*_run.json; Habitat API for diagnostics.
  • Hooks: sync interceptors — validation or clarification at message ingress, turn end, tool return, etc.

Complementary: Pipeline Runner handles scheduled multi-step background work; Hooks handle “may this proceed before/during”; EventBus remains available for future cross-process fan-out but is not on the sleep path.

The desktop companion(桌面伴侣)is an unreachable local app that actively connects to Habitat and registers remote tools in the first-party companion overlay(伴侣浮层 / embedded-overlay;shell provides window/IPC/FS only — not a Node sidecar), with a split boundary:

ConcernHabitat (src/features/companion/)Local install
Behavior, slots, active modelcompanion_profile entity + Habitat RPCCache in ~/.anima/companion/config.json
VRM / VRMA libraryFiles on Habitat host + content-hash metadataLazy download to desktop cache
FBX → VRMAHabitat service onlyNot bundled in desktop installer
Settings UIHabitat RPC + /rpc/v1/companion/* uploadDesktop Settings section (not Habitat)
VRM render, float window, patrolTauri Portal shell + overlay SPA
Agent tools (bubble, play_slot)Habitat RPC tool.* after remote_tools.attachOverlay WebView-host 执行(本地 runtime)

Remote tools ≠ Portal / MCP: Portal shells and Chat/Settings use Habitat RPC for UI only. Dialable peers expose tools via MCP. Remote-tool attach exists solely when Habitat cannot dial the app (companion overlay today; future independent local apps). Routing uses instance_id (same machine may have multiple instances). See companion.mdhabitat-rpc.md.

Capability vision and discussions: GitHub Issues (labels enhancement, discussion, security). This file does not track todos.

  • Principles and structure live here; no concrete task lists
  • Fast-changing behavior follows the running service, not stale prose
  • Actionable work goes in GitHub Issues; close when done