aamp is a self-hosted control layer for LLM agents. It runs as a single Go binary against a local SQLite database, on infrastructure you operate.
ConsoleThe operator console. Every control plane — Agents, Ingress, Helpers, Knowledge, Workflows, Firewalls, Jobs, Logs and Settings — is reachable from one navigation surface.
The host retains authority
The architecture rests on one principle: the host keeps authority. Provider credentials, policy evaluation, tool attachment, audit logging and retrieval all execute host-side. The model chooses among tools the host has exposed — it does not define its own execution policy.
An agent gets capability. The host keeps the keys, the policy, and the record of what happened.
Five defining properties
Five properties distinguish the design. They are the shape of everything else in these docs.
Agent / Ingress separation
An Agent defines behaviour — model binding, system prompt, attached tools, knowledge, session state. An Ingress defines exposure — transport, authentication, network rules, firewalls. They are separate records with a foreign-key relationship, so the same Agent can be exposed through different transports under different policies, or exist with no external exposure at all.
Depth of governance
A thirty-five permission role-based access model with per-resource grants, LLM safety firewalls at two independent boundaries, and fourteen log domains covering every runtime path. This is the property that separates aamp from self-hosted automation platforms, which offer the deployment model without the control surface.
Selective isolation
Isolation is spent where the risk or dependency surface justifies it — three helper workloads (browser automation, document rendering, and retrieval indexing) run as isolated guest jobs; host-native helpers handle everything else. The engineering judgement is where the boundary is drawn, not that one exists.
Layered policy enforcement
Five independent boundaries — host admin auth, transport ingress controls, LLM safety firewalls, isolated helper execution, and proxy-level network filtering — each enforced by host code, each independently logged.
Audit as a by-product
Fourteen log tables record agent turns, tool calls, model requests with token accounting, firewall verdicts, helper executions, job events, proxy traffic, remote commands, knowledge usage, workflow steps and authorization decisions. Traceability is a property of running the system, not a documentation exercise layered on top.
In short
One process, one database, on hardware you control. The model gets tools; the host keeps authority — and everything it does is recorded as it happens.
Intro/Architecture overview
Architecture overview
The entire control plane is one process. There is no message broker, no container orchestrator, no external cache, no separate vector database. State lives in SQLite and the filesystem.
This is a deliberate trade. It rules out horizontal scaling of the control plane, and it makes single-node deployment, backup and audit trivial — the correct trade for the regulated on-premise environments the platform targets.
Control planes
Separation is structural, not conceptual. Each plane has its own tables, its own service package and its own permission set.
Plane
Responsibility
Agent
Behaviour, model binding, prompts, session state, tool and knowledge attachment
Transport
External exposure, authentication, IP rules, transport firewalls
Tool
Host-native and isolated helpers, attachment contracts
OrchestrationThe workflow editor. A deterministic Starlark program orchestrates call_task steps host-side, with the model invoked as one bounded step rather than the pipeline itself.
The Agent / Ingress model
This is the central architectural decision. An Agent is a host-side configuration record — not a running process — carrying its own provider and model binding, system prompt, context-window and compaction settings, a tool-iteration limit, and firewall bindings. Tools and knowledge attach through explicit join tables, so an agent's surface is enumerable rather than inferred at runtime.
An Ingress binds an Agent to a transport. Two kinds exist: an api ingress exposes an OpenAI-compatible endpoint at POST /ingress/{slug}/v1/chat/completions with a bearer key, IP allowlist and firewall bindings; a webwidget ingress delivers a browser chat surface through a tunnel to the separate edge service.
Separating the two produces the properties regulated deployment needs: an Agent can exist with no external exposure; one Agent can be exposed through several ingresses under different policies; revoking exposure is deleting an ingress while agent history survives; and transport policy and behaviour policy are audited separately.
IngressTwo ingress kinds bind an Agent to a transport: an OpenAI-compatible API endpoint, and a public WebWidget tunnel — each with its own keys, network rules and firewalls.
Technology stack
Layer
Choice
Language
Go 1.25.5
HTTP
net/http + chi v5
Templating
templ (compiled Go templates)
Frontend
HTMX + Tailwind, server-rendered; CodeMirror for editors
Database
SQLite via modernc.org/sqlite — pure Go, no cgo
Data access
sqlc-generated from sql/queries.sql
Search
SQLite FTS5
Workflow DSL
Starlark
Proxy / DNS
goproxy · miekg/dns
Crypto
golang.org/x/crypto — AES-GCM, Argon2id
LLM
OpenAI-compatible HTTP
The pure-Go SQLite driver is worth noting: the binary builds without cgo and carries no external database dependency at runtime.
How to start/Login & authentication
Login & authentication
The host GUI is the operator console. Access to it is the first boundary in the platform, and it is enforced entirely by host code.
Reaching the console
aamp binds to 127.0.0.1:3000 by default, overridable with -http-listen. On first run you create the initial host user; subsequent access is through the sign-in form, which establishes a session.
SettingsBehind the session boundary sits the host settings area — global settings, users, LLM providers and secrets. Provider keys entered here stay host-side.
Password storage
Host user passwords are hashed with Argon2id — one-way, never recoverable — with parameters m=65536, t=3, p=2. A password is a credential, not a secret: it can be verified but never decrypted.
Sessions
Session token hashes are stored in host_sessions; the raw token lives only in the browser cookie.
CSRF tokens are required on all browser mutations.
Same-origin checks apply on unsafe methods.
These four controls — hashed passwords, hashed session tokens, CSRF protection and same-origin enforcement — are the host admin boundary. Everything an operator does in the console passes through it.
Note
The console is bound to localhost by default. Exposing it beyond the host is a deliberate deployment choice, not the default — front it with your own reverse proxy and network controls when you do.
How to start/User access levels
User access levels
aamp implements a full role-based access control system — thirty-five discrete permissions across nine resource families, assignable by role and overridable per resource.
The permission catalogue
Permissions are named by what the operator controls, grouped by resource family.
Note the separation inside agents: chat, invoke, expose, configure, create and delete are distinct grants. An operator can be permitted to use an agent without being permitted to change its model, and permitted to configure one without being permitted to expose it externally.
Three levels of grant
Level
Scope
role_permissions
Global by role
role_resource_permissions
A role's grant on one specific resource
user_resource_permissions
A user's grant on one specific resource
Every authorization decision writes to authorization_audit_logs. Who could reach what, and what they were allowed to do with it, is a query — not an investigation.
How to start/Deployment
Deployment
A single Go binary, a SQLite database and local file state. Backup is a file plus two directories; migration is copying them.
Requirements
Linux host with KVM (/dev/kvm)
Guest kernel image and helper images for isolated workloads
Go 1.25.5 and Node.js / npm to build
AAMP_SECRETS_MASTER_KEY decoding to exactly 32 bytes
Runtime state
Path
Holds
aamp.sqlite3
All application data
vms/
Isolated-workload runtime state
blobs/
Kernel, helper images, runtime binaries
Default bind is 127.0.0.1:3000, overridable with -http-listen. For a regulated on-premise deployment, that operational simplicity is a feature, not an omission.
SchedulerThe host scheduler runs workflows and agent prompts on a cron schedule, with a natural-language cron assistant and a preview of the next run times.
Where it fits
Suited to: dedicated on-premise servers; private cloud with nested virtualisation; isolated lab and staging environments.
Not suited to: managed container platforms without KVM; macOS or Windows hosts; any environment where /dev/kvm is unavailable.
Read this before you install
KVM is mandatory. A prospect who discovers the requirement halfway through an install blames the product; one who reads it first makes an informed choice. State requirements plainly and early.
How to start/Security & compliance
Security & compliance
Five independent boundaries, each enforced by host code and each independently logged. Compliance evidence is a by-product of running the system.
Secrets
Two distinct policies, correctly separated:
Recoverable secrets (provider keys, SSH credentials) — AES-256-GCM with a 12-byte nonce, sealed under AAMP_SECRETS_MASTER_KEY, which must decode to exactly 32 bytes. Ciphertext and nonce are stored in separate columns.
Credentials (host user passwords) — Argon2id, one-way, never recoverable.
Secrets referenced by helpers use ON DELETE RESTRICT: a secret in use cannot be deleted out from under a live integration.
SecretsSecrets are encrypted on the host and never shown again after storage. SSH private keys are host-only credentials and are never mounted into guest workloads.
LLM safety firewalls
Firewalls are model-based evaluators applied at message boundaries. Three kinds exist — generic_judge (custom prompt), llama_guard and shield_gemma — each with a direction (inbound or outbound), its own provider and model binding, a policy prompt and a refusal message.
Firewalls attach at two levels: embedded chat on the Agent, and transport on the Ingress. A public widget can therefore run stricter evaluation than the same agent's internal use. Verdicts are logged to firewall_logs, and a test endpoint lets you tune policy before deployment.
FirewallsCreating a firewall: pick a direction and evaluator, write the policy prompt, and set the refusal message shown to end users. Detailed model reasons stay in admin logs.
A defence layer, not a guarantee
Firewalls are model-based evaluators and inherit the reliability characteristics of the models running them. Treat them as one layer among five, not as a promise.
Network controls
Egress passes through a host-controlled proxy with a DNS filter and a domain policy in network_domains supporting default-allow or default-block. HTTP traffic is logged to http_proxy_logs. A controlled subset of host APIs — /llm, /knowledge, /helpers, /environment — is exposed to guests, with provider keys injected host-side at that boundary. This is the mechanism by which a guest can use a model without ever holding a credential.
Knowledge & retrieval
Knowledge bases are host-owned. Documents are ingested and chunked in an isolated one-shot workload, then indexed for host-side retrieval; the chunk and inference prompts are configurable per corpus. Retrieval executes no guest code, and knowledge_usage_logs records which documents answered which query.
KnowledgeA new knowledge base carries its own provider binding, chunk prompt and inference prompt — so indexing strategy is set per corpus rather than globally.
The public edge
The chatwidget edge service is a separate binary intended for DMZ deployment. It holds no database access and no credentials. Its controls include HMAC-derived slugs, a shared-key authenticated tunnel, origin checks, request and message size limits and a per-IP ban window. The tunnel is outbound from the aamp host — the public edge cannot initiate a connection inward.
Observability
Fourteen log domains record runtime activity: agent turns, tool calls, every model request (with token counts split into prompt, cached, completion and total), firewall verdicts, helper executions, job events, knowledge retrieval and sync, workflow runs and steps, proxy traffic, remote commands and authorization decisions. For any answer an agent gives, the retrieved context and the spend behind it are recoverable.
LogsSystem Logs, queryable per domain — Ingress, RAG inference and indexing, Helpers, Workflows, Firewalls, LLM Gateway and Token Spend — filterable by date, agent and session.
Compliance posture
aamp gives your compliance team full data lineage without a documentation project: what entered the prompt, what was retrieved, what the model returned, which tools ran and what it cost — recorded as a by-product of operation.
Under the EU AI Act, Article 50 transparency duties take effect 2 August 2026. Annex III high-risk obligations now run to December 2027. Building the evidence base into the architecture — rather than bolting it on later — is what these dates reward.
On compliance claims
No tool delivers regulatory compliance on its own; compliance is an organisational obligation. What aamp provides is the evidence base — lineage, attribution and audit — that a compliance programme is built on.
Manual/Add a model to your first agent
Add a model to your first agent
An agent needs somewhere to send requests. That means two records: a provider, which is an endpoint plus a credential, and a default model, which every agent inherits unless it binds its own. This walks the whole path once, using OpenRouter as the provider.
Where the key lives
The API key you create below is stored host-side and used host-side. Agents never receive it — they receive capability. Nothing in this procedure moves a credential into a model context.
Before you start
An operator account with Settings permission on the aamp host.
An OpenRouter account. Any OpenAI-compatible provider works the same way — only the base URL changes.
Outbound network access from the host to the provider endpoint. If the host is air-gapped, use a local provider instead and skip part one.
Part one · Create the API key
Do this at the provider first, so you have the key on the clipboard when the aamp form asks for it. OpenRouter shows a key once and never again.
openrouter.ai
Open API Keys
In your OpenRouter workspace, open the API Keys section.
openrouter.ai · api keys
Click New Key
Start a new key rather than reusing one from another integration — a key per integration is what makes revocation cheap later.
new key
Name it after where it will be used
Something like aamp-host-prod. Name it for the consumer, not the key value — the name is all you will see in the list afterwards.
new key
Set an expiration
Pick a date rather than leaving the key open-ended. A rotation you have to schedule is better than one nobody ever performs.
new key · optional
Set a credit limit and a reset period
This is a hard ceiling at the provider, underneath anything aamp enforces. Useful as a second floor: aamp's loop budgets stop runaway agents, and this stops a runaway key.
new key
Create, then copy the key
Copy it straight to the clipboard. It is shown once; if you lose it, delete the key and make another rather than hunting for it.
Part two · Add the provider in aamp
Back on the host. A provider record is an endpoint, a credential and a type — nothing about behaviour, which belongs to the agent.
aamp console
Open Settings → LLM Providers
Providers are host configuration, so they sit in Settings rather than on an agent.
settings · llm providers
Click New LLM Provider
Choose the OpenAI-compatible provider type. OpenRouter speaks that protocol, which is why no dedicated integration is needed.
new llm provider
Name the provider
OpenRouter is fine. This name is what appears in agent model pickers and in Token Spend logs, so make it the name you want to read in an audit.
new llm provider
Enter the base URL
For OpenRouter that is https://openrouter.ai/api/v1 — the API root, with no trailing path. A self-hosted provider takes its own address here, and that is the single field that decides whether requests leave your network.
new llm provider
Paste the API key
Paste the key you copied in part one. It is written to host storage and redacted everywhere it is displayed afterwards.
new llm provider
Mark it default for this type, then Add Provider
Default-for-type means new agents resolve here without being told. Leave it off if you are adding a second provider for comparison and want existing agents untouched.
Part three · Choose the default model
The provider says where requests go. The default model says which one answers when an agent has no binding of its own.
settings
Open Global Settings
The default model is host-wide, alongside the other settings every agent inherits.
global settings
Search the catalogue and pick a model
Open the model field and type part of the name to filter — the recording searches luna and selects openai/gpt-5.6-luna. The list is whatever your provider exposes, so it changes when the provider does.
global settings
Save Settings
Saving writes the default and takes effect on the next agent turn. No restart.
What the agent inherits
Any agent without its own model binding now resolves to this provider and this model. Bind a different model on the agent itself when one workload needs a larger or cheaper one — that binding wins over the global default.
From the first turn onwards, every request is attributed: Logs → LLM Gateway for the request and response, Logs → Token Spend for tokens and cost per agent and session. If the provider rejects the key, the failure appears there rather than in the agent's reply.
Rotating the key
Replace the credential on the provider record and save; agents pick it up on their next turn. Delete the old key at the provider afterwards, not before — a revoked key mid-run surfaces as a failed turn in the log.