One brain. Every agent. Nothing forgotten.
A self-hosted personal memory layer for AI agents. SQLite (or Postgres) + MCP + llama.cpp.
Zero-config by default; one file holds your memories, wiki, and code graph.
Your context, always available.

• Download 👉 Aperio-lite for non-code users. • Small tool for big ideas • How to Install & Use?

• 🌐 Site: https://baiganio.github.io/aperio

Downloads Latest Release GitHub contributors Last Commit CodeQL codecov Quality Gate Status Codacy Badge Dependabot Security Policy


🏗️ (Quick) Project Structure

📂 aperio/          <---=  You are here if You are Developer. He-he ;/
├── 📂 db/
│   ├── index.js                  # Store factory — auto-selects Postgres or SQLite
│   ├── sqlite.js                 # SQLite + sqlite-vec + FTS5 adapter (zero config, default)
│   ├── postgres.js               # Postgres + pgvector adapter (Docker)
│   ├── types.js                  # Shared DB types
│   ├── 📂 migrations/            # Postgres SQL (memories + wiki + codegraph)
│   └── 📂 migrations-sqlite/     # SQLite SQL (same schemas, FTS5 + vec0)
├── 📂 docker/
│   └── docker-compose.yml        # pgvector/pgvector:pg16
├── 📂 docs/
│   └── index.html                # Landing page for GitHub Pages
├── 📂 id/
│   └── whoami.md                 # Instructions for AI agent identity (edit this!)
├── 📂 lib/
│   ├── agent.js                  # Agent entry point — provider loops live in lib/agent/providers/
│   ├── 📂 agent/                 # Provider loops, tool hooks, profiles, lifecycle middleware
│   ├── terminal.js               # Terminal chat client
│   ├── 📂 emitters/              # CLI and WebSocket stream emitters
│   ├── 📂 handlers/              # Attachment and memory handlers
│   ├── 📂 helpers/               # Embeddings, logger, port, shutdown, llama.cpp health
│   ├── 📂 context/               # Context trimming + private large-result artifact storage
│   ├── 📂 security/              # Durable interrupt service for resumable sensitive actions
│   ├── 📂 routes/                # Express API routes + path safety guards
│   ├── 📂 utils/                 # Chat utilities
│   └── 📂 workers/               # Deduplication, reasoning adapters, skill loader
├── 📂 lib/codegraph/             # Pre-indexed code knowledge graph (symbols, calls, imports)
│   ├── indexer.js                # Backend dispatcher (Postgres or SQLite)
│   ├── watcher.js                # chokidar-backed live reindex
│   ├── extract-ts.js             # tree-sitter JS/TS/JSX/TSX extractor
│   └── 📂 backends/              # postgres.js · sqlite.js
├── 📂 mcp/
│   ├── index.js                  # MCP server entry point
│   └── 📂 tools/
│       ├── memory.js             # remember · recall · update_memory · forget · backfill_embeddings · deduplicate_memories (6)
│       ├── self-memory.js         # self_remember · self_recall · self_update · self_forget (4)
│       ├── files.js              # read_file · grep_files · write_file · append_file · edit_file · read_docx · scan_project · delete_file · generate_xlsx · generate_docx (10)
│       ├── wiki.js               # wiki_write · wiki_search · wiki_list · wiki_get (4)
│       ├── codegraph.js          # code_search · code_outline · code_context · code_callers · code_callees · code_repos (6)
│       ├── docgraph.js           # doc_search · doc_repos · doc_outline · doc_context · doc_refs (5)
│       ├── shell.js              # run_node_script · run_python_script · run_shell · syntax_check (4)
│       ├── web.js                # fetch_url · web_search (2)
│       ├── image.js              # read_image · preprocess_image · describe_image (3)
│       ├── github.js             # fetch_github_issue · create_github_issue · update_github_issue · list_github_issues · record_issue_triage (5)
│       ├── data.js               # export_data · import_data (2)
│       └── database.js           # db_connections · db_schema · db_query · db_execute (4)
├── 📂 public/
│   └── index.html                # Web UI — themes, streaming, sidebar
├── 📂 skills/                    # Memory, reasoning, tools, coding standards, etc.
├── 📂 tests/      
├── .env.example                  # Essentials + bootstrap only (everything else → Settings overlay)
├── package.json                  # Dependencies
└── server.js                     # Express + WebSocket + agent loop
 

💡 Tip: whoami.md controls the identity of the AI agent.

  • It is the most impactful file to customize.

Getting Started

Three ways to install

For whom How
1 · Aperio-lite Non-coders — no terminal, ever Download the zip, unzip, double-click START — a browser wizard installs everything and picks a model for your machine. See the lite guide.
2 · One command Terminal users who want painless updates curl -fsSL https://raw.githubusercontent.com/BaiGanio/aperio/release/.github/lite/install.sh | bash — clones the release branch and starts Aperio. Re-run anytime to update in place; your memory database is preserved.
3 · From source Contributors / full control git clone -b dev + npm install — the steps below.

Methods 1–2 install from the release branch (and the release zip), published on each release. The steps below cover method 3.

Prerequisites

Step 1. Clone & Configure Environment Variables

Dedicated dev branch stripped from the file/folder noise. Only what's needed.

# dedicated developer branch - no extra files
git clone --depth 1 -b dev https://github.com/BaiGanio/aperio.git
cd aperio

# restore dependencies
npm install

Ready to use .env.example for a fully local setup. The template is tiny — just the essentials plus bootstrap plumbing; **everything else is configured in the app's Settings overlay (see below), not in .env:

# cp .env.example .env

AI_PROVIDER=llamacpp
LLAMACPP_MODEL=Qwen/Qwen2.5-3B-Instruct-GGUF:Q4_K_M
# DB_BACKEND=sqlite               # default (auto-detected); uncomment to force
# SQLITE_PATH=./sqlite/aperio.db  # default location for the single-file DB

Embeddings default to local transformers (no key) — switch to Voyage, or change any other setting, in the Settings overlay.

Step 2. Databases & Migrations

Aperio supports two storage backends. You don't need to choose — auto-detect picks the right one based on whether Docker is running:

Backend When to use Requires
SQLite + sqlite-vec (default) No Docker, quick start, single user. Single file at var/aperio.db. Nothing extra
Postgres + pgvector Multi-agent, persistent, production-like Docker
# SQLite is the default — no extra steps needed.
# Skip the Docker commands below and go directly to Step 3.

💡 Tip: Set DB_BACKEND=sqlite in .env to force SQLite, or DB_BACKEND=postgres for Postgres.
If not set, Aperio auto-detects: uses Postgres when Docker is running, SQLite otherwise.

# POSTGRES MODE — start the database and run migrations (run from the repo root)
# --env-file .env is required: the compose file lives in docker/ but .env is at
# the repo root, and Compose only looks for .env next to the compose file.
docker compose -f docker/docker-compose.yml --env-file .env up -d
npm run migrate

# PRODUCTION — full stack (app + Postgres) in one go. Same --env-file rule applies.
docker compose -f docker/docker-compose.prod.yml --env-file .env up -d

☁️ Hosted Postgres (Supabase, Neon, Azure, …): auto-detect only recognises the local Docker container — it will not find a remote database. Select the backend explicitly and point DATABASE_URL at your host:

DB_BACKEND=postgres
DATABASE_URL=postgresql://user:pass@host:5432/db?sslmode=require

Then run npm run migrate. The host must allow the vector (pgvector ≥ 0.5) and pgcrypto extensions — on Azure Flexible Server, add both to the azure.extensions server parameter first.

Step 3. Local AI Engine — Nothing to Install

💡 Tip: Skip this step entirely when using a cloud or CLI-backed AI_PROVIDER. For AI_PROVIDER=llamacpp, there is no separate engine to install or run — Aperio vendors and manages llama-server itself: it downloads the pinned binary on first run, spawns and monitors it, and downloads the GGUF model you pick (or the one LLAMACPP_MODEL names) the first time it's needed.

LLAMACPP_MODEL=Qwen/Qwen3-30B-A3B-GGUF:Q4_K_M     # strong reasoning, MoE, best tool-calling
# LLAMACPP_MODEL=ggml-org/gemma-4-12B-it-GGUF:Q4_K_M   # general-purpose, dense
# LLAMACPP_MODEL=Qwen/Qwen2.5-3B-Instruct-GGUF:Q4_K_M  # lightweight fallback, runs anywhere

Step 4. Start Aperio Web UI

npm run start:local              # localhost:31337 → browser opens automatically

The Web UI includes a flag-based switcher for all 24 official EU languages, plus Chinese (中文) and Japanese (日本語). Every locale ships with the complete 304-key interface catalog, and your selection persists across restarts. Contributors can verify locale parity, placeholders, HTML tags, and statically referenced UI keys with npm run i18n:check.

Step 5. Start Aperio terminal chat

npm run chat:local               # runs as proxy or standalone

That's it. No API keys. No cloud. Full semantic memory on your machine.

Installation smoke tests

Contributors with an Apple Silicon Mac can verify fresh ARM64 installs in disposable Parallels environments. See vms/README.md for prerequisites, snapshot setup, logs, and troubleshooting:

npm run vmtest:linux          # Ubuntu ARM64 + one-liner installer
npm run vmtest:linux:debian   # Debian ARM64 + development install
npm run vmtest:windows        # Windows 11 ARM + clean snapshot

Test suites

Use npm test for the complete local suite. Push and pull-request CI runs unit/integration coverage plus the lightweight E2E dashboard suite, while real server fixtures remain opt-in:

npm run test:unit       # Unit/integration tests, excluding tests/e2e
npm run test:e2e        # Protocol and real-app E2E, concurrency capped at 2
npm run test:e2e:real   # Focused production-server E2E only
npm run test:e2e:ci     # Dashboard E2E suite, excluding real-app fixtures
npm run test:ci:unit    # Unit/integration coverage used by Codecov CI

The real-app fixtures use temporary SQLite databases, ephemeral localhost ports, and a test-agent stub; no model service is required. Postgres E2E parity is enabled only when APERIO_E2E_POSTGRES_URL is configured. To run them in GitHub Actions, manually dispatch the Real-app E2E (manual) workflow.

Model-tier benchmark campaigns (maintainers)

The model-tier pilot measures whether a local llama.cpp model can use Aperio's tools correctly, complete fixed qualification cases, and stay within the RAM and context budget assigned to an 8, 16, 24, or 32 GB tier. It is useful when comparing candidate installer defaults or diagnosing a model/tier failure. It is not a general-purpose speed benchmark, and one pilot run is not enough to promote an installer default.

Validate the catalog and cases without starting any model process:

npm run model-tier:pilot -- --validate

Run one isolated case for one model and tier. Use a fresh campaign ID for an audit so the private evidence cannot overwrite another run:

npm run model-tier:pilot -- \
  --model gemma4-e4b-ud-q4kxl \
  --tier 16 \
  --case chain-recall-wiki \
  --campaign audit-YYYYMMDD-gemma-e4b-16gb-chain-recall

The model catalog is in .github/model-tiers/models.json; supported tier values are 8, 16, 24, and 32. The runner starts an isolated temporary SQLite app/workspace, imports the qualification fixture, waits for app/model/ embedding/graph readiness, runs the selected case, and tears the processes and temporary workdir down afterward. Private evidence is written to:

var/benchmarks/model-tiers/<tier>gb/<model>/<campaign-id>/

Keep this directory private and never commit it. Important files are run.json, cases.jsonl, transcript.jsonl, application.log, llamacpp.log, and metrics.csv. Inspect the logs before rerunning: a exceed_context_size_error is explicit context-limit evidence and makes the case invalid rather than a model-quality failure, even if the application also emitted a completed terminal turn. It is not behaviorally retried. A whole-turn deadline without that evidence is a generic loop timeout. Missing app/model/ fixture/graph readiness is a harness/readiness failure; a case that passes tool-sequence and state assertions is a valid completion. The runner may retry an ordinary behavioral failure after restoring the isolated fixture, so classify the final persisted case result while retaining the first-attempt diagnostics.

For a controlled tier audit, run cases sequentially and use a separate campaign ID per placement. Test tiers in descending order (32 → 24 → 16 → 8) so high-capability failures are diagnosed before spending time on smaller placements. If both the 32 GB and 24 GB tiers end in genuine model/behavior failures, stop the audit, preserve and inspect their required artifacts, and do not continue to 16 GB or 8 GB until the failure is understood. A harness or readiness failure is invalid evidence and must not trigger this early stop. Qualification cases retain a five-minute whole-turn deadline by default so ordinary foreground load does not turn a near-complete local-model response into a false timeout. For high-tier audits, prioritize the catalog's primary Gemma placements: gemma4-26b-a4b-ud-q4kxl and gemma4-e4b-ud-q4kxl. Afterward, use the non-live aggregation/finalist workflow (--aggregate, --finalists, and --decide --evidence <path>) only on valid, comparable evidence. Do not treat a single pilot pass as a tier recommendation; the full-exam manifest and human review are required before changing defaults.

💡 New to the chat? Type help for a guided tour — every command comes with a runnable try: example. Type help <command> (e.g. help attach) for focused docs, or examples to hide/show the example lines (your choice sticks). Prefer another language? lang de (or set APERIO_UI_LANG in .env) localizes the welcome/help text — English is the default and per-string fallback. Want a clean slate? restart starts a fresh conversation; restart --hard relaunches.

💡 Configure from the Web UI — no .env editing required. The sidebar Config button opens the full-screen Settings overlay: plain-language categories, search, and a Simple↔Advanced toggle, with every setting as a typed control (toggle / select / number / text / chips / secret): pick your AI_PROVIDER, paste API keys, switch embeddings, toggle the code/doc graph, and more. Values are saved to the database — the UI is authoritative out of the box (precedence: DB > .env > default; set APERIO_CONFIG_PRECEDENCE=env to make the file win, e.g. to keep API keys env-only) — and apply after a restart (a banner reminds you). Bootstrap/security plumbing (ports, DB creds, TLS, auth token) stays read-only here — those live in .env. Every variable is documented in the generated docs/config-reference.md; any of them hand-written into .env still works. Run npm run config:sync to import hand-added vars into the overlay. The same overlay also contains the Allowed folders, Database connections, and GitHub triage views; their existing flows remain available without opening a separate drawer.

Q: Now what?

💡 Stuck on the installation steps? — check Troubleshooting wiki.

💡 Check Aperio MCP Tools Guide wiki for extended examples.
💡 Check Commands wiki for the available options to run the app.


Architecture

Q: Feel a need to read?

💡 Tip: Visit Architecture & Design for in-depth explanations.


Round-table mode (two-agent cross-review)

Aperio can boot a second agent alongside the primary so that any chat turn can be cross-reviewed before it reaches you. Two agents take turns: Agent A answers, Agent B reviews, A revises, B re-reviews — until they reach explicit AGREED or a hard round cap is hit. A single consensus bubble is rendered when they agree; otherwise both positions are shown side-by-side.

Enable it in the Settings overlay (Round-table section), or with ROUNDTABLE_AGENTS in .env:

# Format: provider:model,provider:model
ROUNDTABLE_AGENTS=anthropic:claude-haiku-4-5-20251001,deepseek:deepseek-chat
ROUNDTABLE_MAX_ROUNDS=3

When set, the chat UI gains a Discuss toggle next to the send button. Toggle it ON to route the next turn through the round-table; OFF behaves identically to single-agent chat. If ROUNDTABLE_AGENTS is unset or only one pair parses, the toggle stays disabled and the app behaves exactly as before. Internally, each round-table participant is created from the same validated AgentSpec contract used by the main agent, preserving provider/model and character behavior while keeping the runtime contract explicit.

Personas live in id/whoami-primary.md and id/whoami-verifier.md — edit them to tune how each agent answers or critiques.

Domain characters layer expertise on top of each agent's role. Set ROUNDTABLE_CHARACTERS to a comma-separated pair of slugs (first → Agent A, second → Agent B). Each slug resolves to id/characters/<slug>.md.

When you want… Set ROUNDTABLE_CHARACTERS to
Code review software-architect,code-reviewer
Security audit software-architect,security-engineer
Product decision product-thinker,software-architect
Open-ended question socratic-questioner,software-architect
Domain-specific space-engineer,doctor

Available characters: software-architect, code-reviewer, security-engineer, product-thinker, socratic-questioner, doctor, space-engineer. Add your own by dropping a .md file into id/characters/.

Manifestos. After each round-table concludes (consensus or not), each agent writes a personal manifesto — a short, opinionated final statement. Both are saved to var/roundtables/aperio-manifesto-{sessionId}.md and served at /roundtables/ for preview and download. Manifesto generation is best-effort; it never blocks the round-table result from reaching you.


Code Graph

Aperio ships a pre-indexed code knowledge graph so an agent can query your codebase instead of reading 50 files to answer "who calls X?" or "where is Y defined?". Symbols, calls, imports, and extends edges are extracted with tree-sitter (JS / TS / JSX / TSX) and stored alongside your memories.

Two ways to use it:

# 1. One-shot index of the current directory
node lib/codegraph/indexer.js .

# 2. Live mode — start the server with a file watcher that reindexes on save
APERIO_CODEGRAPH=on npm run start:local

The graph respects APERIO_ALLOWED_PATHS_TO_READ, so you can index multiple repos at once (e.g. Aperio + a side project). The sidebar in the web UI has a "Code" panel for searching symbols and walking callers / callees visually; the model uses the same data via the code_* MCP tools listed below.

Backend support: Postgres and SQLite both work. With SQLite (the default), the graph lives in the same var/aperio.db file as your memories.


MCP Tools

Aperio exposes 59 tools across 13 categories over MCP. Any MCP-compatible agent (Cursor, Windsurf, Claude, etc.) can call them.

Category Tool What it does
Memory remember Save a memory with type (fact, preference, workflow, …), title, tags, importance, and optional expiry
recall Semantic or full-text search across all memories
update_memory Update an existing memory by ID; tombstones the old version, re-generates its embedding
forget Delete a memory by ID
backfill_embeddings Generate embeddings for memories that are missing one
deduplicate_memories Find and merge near-duplicate memories by cosine similarity
Self-Memory self_remember Save a note to the agent's own walled-off store — separate from user memory
self_recall Search the agent's own notes semantically or by full-text
self_update Revise one of the agent's own notes in-place by ID
self_forget Delete one of the agent's own notes by ID
Wiki wiki_write Create or update a wiki article (LLM-authored, cited synthesis); upserts by slug, bumps revision
wiki_search Hybrid full-text + semantic search over articles — call before wiki_write
wiki_list Browse articles newest-first by tag / status / updated_since
wiki_get Fetch a full article by slug, with breadcrumb and optional stale-refresh
Code Graph code_search Hybrid FTS + semantic search over pre-indexed symbols (functions, classes, methods, consts)
code_outline List every symbol in a file by line — cheap map before reading
code_context Fetch the source slice for a qualified symbol, with leading doc and line padding
code_callers Walk the reverse call graph (depth-capped) — who calls this?
code_callees Walk the forward call graph (depth-capped) — what does this call?
code_repos List indexed repos with file / symbol counts and last-indexed timestamp
Doc Graph doc_search Semantic + FTS search over pre-indexed document passages
doc_repos List indexed doc folders with chunk counts and by-mime breakdown
doc_outline Section tree (TOC) for one document — cheap map before fetching
doc_context Fetch text of one section or chunk by id
doc_refs Cross-document reference lookup (IDs, URLs, citations)
Files read_file Read a code or text file (max 500 lines per call, paginated via offset)
grep_files Recursively find literal text in allowed code/text files; returns relative paths and line numbers while skipping secrets, symlinks, dependencies, and build output
write_file Create or overwrite a file (subject to write-path guard)
append_file Append content to an existing file without touching the rest
edit_file Replace an exact string in a file (replace_all for multiple occurrences)
read_docx Read and extract text from .docx files
scan_project Traverse a project folder — returns a file tree and reads key files
delete_file Delete a file by path (subject to write-path guard)
generate_xlsx Generate a multi-sheet .xlsx workbook, served for download
generate_docx Create .docx Word documents programmatically
Shell run_node_script Run a .js script inside an allowed write path; returns its output
run_python_script Run a .py script inside an allowed write path; returns its output
syntax_check Check a JavaScript file for syntax errors without executing it
run_shell Execute a shell command with output capture
Web fetch_url Fetch a URL, strip HTML, truncate at 15 000 characters
web_search Search the web via DuckDuckGo, return ranked results
Image read_image Load an image (file path or base64) for the agent to analyze
preprocess_image Normalize an image to RGB PNG before sending to a local VLM (strips alpha, letterboxes to 896×896)
describe_image Send an image to a local llama.cpp vision model (VLM) and return a text description
GitHub fetch_github_issue Read a GitHub issue with comments and metadata
create_github_issue Open a new issue on a GitHub repository
update_github_issue Edit an existing GitHub issue
list_github_issues List open issues for triage across repos
record_issue_triage Record a triage verdict for an issue
Data export_data Export memories & wiki to a portable JSON file
import_data Import memories & wiki from a previously-exported JSON file
Database db_connections List available external database connections (never exposes credentials)
db_schema Introspect tables, columns, indexes, and foreign keys
db_query Run ONE read-only SQL statement with parameterized bindings
db_execute Propose a write/DDL statement — confirm-before-write flow

.docx and .xlsx files can also be generated via the skills/docx/ and skills/xlsx/ skill scripts. PowerPoint files are generated by writing a script (see skills/pptx/) and running it via run_node_script — there is no dedicated pptx tool.

💡 Tip: Check Aperio MCP Tools Guide for call examples.

Large tool results

When a shell command, file read, or other tool returns more content than the model can safely keep in context, Aperio stores the complete redacted result in a private session/run artifact and gives the model a bounded head/tail preview with its artifact ID. This prevents a single large result from displacing the conversation while preserving the full local output. After the first offload in a run, Aperio attaches the read-only read_artifact tool. It reads the stored result by byte offset and limit, reports next_offset, and never accepts a session or run owner from the model.

The effective token threshold is capped at 25% of the active model's context window. Retrieval defaults to 8,192 content bytes per call, accepts at most 24,000 content bytes, and caps the complete response at 32,000 bytes. Both offload thresholds are editable in the Settings overlay:

APERIO_TOOL_RESULT_OFFLOAD_TOKENS=20000
APERIO_TOOL_RESULT_OFFLOAD_BYTES=80000

Artifacts are private files under var/agent-artifacts/. Session artifacts follow SESSION_RETENTION_DAYS (14 days in the default configuration) and are removed immediately when their session is deleted. Run artifacts follow AGENT_RUN_RETENTION_DAYS; unset or 0 retains run history and artifacts indefinitely. Logs and background-run history record only artifact count, byte count, ID/scope, and source tool—not stored content.

To test result offloading, retrieval, observability, and retention:

# Focused automated coverage
NODE_ENV=test node --test \
  tests/lib/context/artifactStore.test.js \
  tests/lib/context/toolResultOffload.test.js \
  tests/lib/context/artifactRetrieval.test.js \
  tests/lib/agent/tool-hooks.test.js \
  tests/lib/helpers/sessions.test.js \
  tests/lib/workers/agent-run-prune.test.js \
  tests/lib/workers/agent-scheduler.test.js

# Database migration/history coverage
NODE_ENV=test node --test tests/db/sqlite.test.js tests/db/postgres.test.js

For a manual check, temporarily set APERIO_TOOL_RESULT_OFFLOAD_BYTES=1000, restart Aperio, and ask a capable model to read a text file larger than 1 KB. Confirm the preview contains an artifact ID, a subsequent model iteration offers read_artifact, and the server log contains [tool-result-offload] metadata without the file contents. Delete the chat from History and confirm its directory under var/agent-artifacts/sessions/ is removed. Restore the normal threshold after the check.

Agent lifecycle middleware

lib/agent/middleware.js defines Aperio's provider-neutral orchestration contract: beforeModel, selectTools, beforeTool, afterTool, afterModel, onInterrupt, and onError. Named middleware runs in registration order against immutable request snapshots. Hooks may return a shallow update or explicitly stop the chain; failures retain hook and middleware identity while notifying every error observer.

Tool calls now pass through named safety middleware for failure-budget gating, repeated-call detection, untrusted-content fencing, taint propagation, and the taint-to-confirm signal on writes. Existing event payloads and safety limits are preserved.

The native Anthropic, llama.cpp, Gemini, and DeepSeek loops also receive one canonical context composed by named middleware: bounded/trimmed messages, memory pointers, matched skill prompts, selected canonical MCP tools, and losslessly offloaded large results. Each provider adapter still owns its wire format and secret-redaction boundary. Claude Code and Codex retain their SDK/CLI-managed context paths.

Each native run keeps a bounded in-memory lifecycle trace for diagnostics. agent.getLifecycleTrace() returns the latest run's read-only entries and retention statistics. Entries include only hook/middleware identity, relative timing, decision (continue, update, stop, or error), and error class. The trace retains at most 200 entries and never stores prompts, tool arguments, tool results, exception messages, secrets, or artifact contents.

lib/security/interruptService.js provides the durable foundation for resumable sensitive actions. Pending action descriptors are stored in the same SQLite/Postgres backend as sessions and background runs with tool name, canonical arguments or a protected payload reference, digest, allowed decisions, status, timestamps, expiry, and claim/completion metadata. The service supports approve, edit, reject, and respond decisions; repeated identical decisions are idempotent, conflicting replays are rejected, and approved/edited actions must be atomically claimed before execution so they cannot run twice. File write/append/edit/delete confirmations now use this service while preserving the existing wr_... / del_... token UX, capped edit diffs, tainted-turn confirmation, and clean scratch-workspace auto-writes. Database writes through db_execute use the same durable service with JSON-stored connection name, normalized SQL, bound params, statement class, and commit-time revalidation of SQL classification plus connection write permissions. The Web UI and /api/interrupts surface pending descriptors after reconnect and support approve, safe JSON argument editing, reject with optional feedback, and respond without execution; approve/edit still atomically claim and revalidate before any file or database change runs.

Fresh SQLite and Postgres stores also seed a disabled nightly-maintenance background-agent example. It runs backfill_embeddings followed by dry-run deduplicate_memories when the user explicitly enables the job and background agent scheduler. Freeform background jobs persist their provider/model, persona, character, timeout, and tool policy through validated AgentSpec definitions; older job records are normalized on read/write.

Advanced agent setups can also be packaged as a portable bundle directory and passed to createAgent({ bundleDir }). A bundle may contain AGENT.md, permissions.json, memory-scopes.json, output.schema.json, and local skills/; explicit administrator specs still bound tools, filesystem/memory permissions, interrupts, recursion, concurrency, and timeouts.

NODE_ENV=test node --test \
  tests/lib/agent/middleware.test.js \
  tests/lib/agent/model-context-middleware.test.js \
  tests/lib/agent/tool-hooks.test.js

Philosophy

Aperio is open source and self-hosted because your memories is yours.

  • It runs entirely on your machine - no API keys, no data leaving your network, no cloud dependency.
  • Default is local and private. The option - self-hosted. The price - free forever.
  • Cloud AI is available as a power upgrade, but you will be never forced to use it.
🔒 Local by default ☁️ Cloud as upgrade
llama.cpp + local embeddings — zero external calls Claude / DeepSeek for deep research & heavy tasks
🗄️ Your brain, your data 🖥️ MCP-native
Postgres or SQLite lives on your machine. You own it. Any MCP agent plugs in — Cursor, Windsurf, etc.
Free to run
No subscription. No per-message cost. Just your hardware.

‼️ What Aperio Is Not

Deployment model:

🚫 Not a cloud service 🚫 Not a managed product
No hosted version, no SaaS, no managed infra No support contracts, SLAs, or guaranteed uptime
🚫 Not plug-and-play 🚫 Not production-hardened
Needs Node.js and basic terminal comfort Early software, built in the open, improving fast

Feature scope — what Aperio will never become:

🚫 Not a chat app 🚫 Not a general-purpose AI agent
The bundled Web UI and terminal client are conveniences for setup and inspection — not the product. Aperio is an MCP server first. Aperio provides memory, wiki, and code-graph tools TO agents. It does not replace the agent itself.
🚫 Not a replacement for Claude, Cursor, or Windsurf 🚫 Not a multi-tenant SaaS platform
Aperio is a memory layer that sits alongside your existing AI tools. It augments them — it does not compete with them. Single-user, single-machine by design. No accounts, no organizations, no billing system. Will stay that way.
🚫 Not a plugin or extension 🚫 Not a "build everything" platform
It's a self-hosted server you run yourself — not something you install into another app. Aperio says no to feature ideas that dilute the core: memory + code graph for MCP agents. If a feature doesn't serve that sentence, it doesn't ship.

AI Providers

Switch in the Settings overlay (AI_PROVIDER), or with a single line in .env. Everything else — memories, tools, UI — stays identical.

AI_PROVIDER=llamacpp     # "llamacpp" | "anthropic" | "deepseek" | "gemini" | "claude-code" | "codex"

⬡ llama.cpp (Default — Local, Free, Private)

No API keys, no data leaving your machine. Aperio vendors and fully manages the llama-server engine itself — no separate install, no daemon to run.

AI_PROVIDER=llamacpp
LLAMACPP_MODEL=Qwen/Qwen2.5-3B-Instruct-GGUF:Q4_K_M
LLAMACPP_BASE_URL=http://127.0.0.1:8080

Recommended models (an HF repo[:quant] string — downloaded automatically on first use):

Model Best for
Qwen/Qwen2.5-3B-Instruct-GGUF:Q4_K_M Default — lightweight, runs anywhere (≥ 8 GB RAM)
ggml-org/gemma-4-E4B-it-GGUF:Q4_K_M General-purpose, good tool-calling (≥ 8 GB RAM)
ggml-org/gemma-4-12B-it-GGUF:Q4_K_M Stronger general-purpose (≥ 24 GB RAM)
Qwen/Qwen3-30B-A3B-GGUF:Q4_K_M Heavy reasoning, MoE — fast even on modest hardware via expert offload (≥ 48 GB RAM, or use the fast-low-vram perf profile)

💡 Tip: Aperio detects your RAM and flags the best-fitting model as (recommended) — in the setup wizard and in the terminal model picker — so you don't have to guess. APERIO_LOCAL_PERF_PROFILE (balanced | fast-low-vram | long-context | quality) tunes the pick and the engine's launch flags for your hardware.

llama.cpp vision memory behavior

When LLAMACPP_VLM_MODEL is enabled, Aperio estimates the resident footprint of t