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 •
🏗️ (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.mdcontrols 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
releasebranch (and the release zip), published on each release. The steps below cover method 3.
Prerequisites
- Node.js 18+ — download from https://nodejs.org/en/download
- Docker Desktop — (optional, for Postgres mode)
- (nothing to install for local AI — Aperio vendors and manages its own llama.cpp engine)
- Anthropic API key — (optional, for cloud AI)
- DeepSeek API key — (optional, for cloud AI)
- Google Gemini API key — (optional, for cloud AI)
- OpenAI Codex CLI — (optional; use cached login or
CODEX_API_KEY) - Voyage AI API key — (optional, for cloud embeddings)
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.examplefor 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=sqlitein.envto force SQLite, orDB_BACKEND=postgresfor 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_URLat your host:DB_BACKEND=postgres DATABASE_URL=postgresql://user:pass@host:5432/db?sslmode=requireThen run
npm run migrate. The host must allow thevector(pgvector ≥ 0.5) andpgcryptoextensions — on Azure Flexible Server, add both to theazure.extensionsserver parameter first.
Step 3. Local AI Engine — Nothing to Install
💡 Tip: Skip this step entirely when using a cloud or CLI-backed
AI_PROVIDER. ForAI_PROVIDER=llamacpp, there is no separate engine to install or run — Aperio vendors and managesllama-serveritself: it downloads the pinned binary on first run, spawns and monitors it, and downloads the GGUF model you pick (or the oneLLAMACPP_MODELnames) 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
helpfor a guided tour — every command comes with a runnabletry:example. Typehelp <command>(e.g.help attach) for focused docs, orexamplesto hide/show the example lines (your choice sticks). Prefer another language?lang de(or setAPERIO_UI_LANGin.env) localizes the welcome/help text — English is the default and per-string fallback. Want a clean slate?restartstarts a fresh conversation;restart --hardrelaunches.
💡 Configure from the Web UI — no
.envediting 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 yourAI_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; setAPERIO_CONFIG_PRECEDENCE=envto 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 generateddocs/config-reference.md; any of them hand-written into.envstill works. Runnpm run config:syncto 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 |
.docxand.xlsxfiles can also be generated via theskills/docx/andskills/xlsx/skill scripts. PowerPoint files are generated by writing a script (seeskills/pptx/) and running it viarun_node_script— there is no dedicatedpptxtool.
💡 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
No comments yet
Be the first to share your take.