aamp Docs
Intro/What is aamp?

What is aamp?

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.

The aamp operator console showing the helpers catalogue
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.

Deployment shape

┌────────────────────────────────────────────────┐
│ Linux host (KVM required)                        │
│  ┌─────────────────────────────────────────────┐ │
│  │ aamp — single Go binary                      │ │
│  │   GUI (chi + templ + HTMX)      :3000        │ │
│  │   Ingress API (OpenAI-compatible)            │ │
│  │   Workflow engine (Starlark)                 │ │
│  │   Scheduler (cron)                           │ │
│  │   Egress proxy + DNS filter                  │ │
│  │   Secrets (AES-GCM, Argon2id)                │ │
│  └─────────────────────────────────────────────┘ │
│         │                       │                 │
│   ┌─────┴──────┐        ┌───────┴────────┐        │
│   │ SQLite     │        │ Isolated       │        │
│   │ 74 tables  │        │ helper         │        │
│   │ + FTS5     │        │ workloads (3)  │        │
│   └────────────┘        └────────────────┘        │
└────────────────────────────────────────────────┘
                 │
          ┌──────┴───────┐
          │ chatwidget   │  optional edge binary (DMZ)
          └──────────────┘

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.

PlaneResponsibility
AgentBehaviour, model binding, prompts, session state, tool and knowledge attachment
TransportExternal exposure, authentication, IP rules, transport firewalls
ToolHost-native and isolated helpers, attachment contracts
PolicyFirewall evaluation, network filtering, RBAC, sandbox rules
OrchestrationSessions, workflows, schedules
AuditStructured logs across every runtime domain
The workflow editor with a Starlark program
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.

The ingress catalogue showing API and WebWidget ingress types
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

LayerChoice
LanguageGo 1.25.5
HTTPnet/http + chi v5
Templatingtempl (compiled Go templates)
FrontendHTMX + Tailwind, server-rendered; CodeMirror for editors
DatabaseSQLite via modernc.org/sqlite — pure Go, no cgo
Data accesssqlc-generated from sql/queries.sql
SearchSQLite FTS5
Workflow DSLStarlark
Proxy / DNSgoproxy · miekg/dns
Cryptogolang.org/x/crypto — AES-GCM, Argon2id
LLMOpenAI-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.

The Settings area with the LLM provider configuration form
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.

Platform
dashboard.viewaudit.viewiam.managesettings.general.manage
Providers & secrets
providers.useproviders.managesecrets.usesecrets.manage
Agents
agents.chatagents.invokeagents.exposeagents.configureagents.createagents.delete
Workflows
workflows.executeworkflows.configureworkflows.createworkflows.delete
Knowledge
knowledge.readknowledge.useknowledge.content.manageknowledge.configureknowledge.createknowledge.delete
Helpers
helpers.usehelpers.configurehelpers.createhelpers.delete
Ingresses
ingresses.configureingresses.createingresses.delete
Firewalls
firewalls.usefirewalls.testfirewalls.configurefirewalls.createfirewalls.delete

Granularity within a resource

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

LevelScope
role_permissionsGlobal by role
role_resource_permissionsA role's grant on one specific resource
user_resource_permissionsA 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

PathHolds
aamp.sqlite3All 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.

The Create Job form with a cron schedule
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.

The stored-secrets form in Settings
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.

The Create New Firewall form with a policy prompt
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.

The New Knowledge Base form with chunk and inference prompts
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.

The System Logs view with domain tabs and filters
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 sidebar with API Keys highlighted
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.

The New Key button on the OpenRouter API keys page
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.

The Name field in the OpenRouter new key dialog
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.

The expiration menu open in the new key dialog
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.

Credit limit and reset frequency fields in the new key dialog
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.

The copy-to-clipboard control beside a newly created key

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.

The LLM Providers tab in aamp Settings
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.

The New LLM Provider form in aamp Settings
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.

The Name field in the New LLM Provider form
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.

The Base URL field in the provider form
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.

The API key and Base URL fields filled in
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.

The set-as-default checkbox and Add Provider button

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.

The Global Settings tab in aamp Settings
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.

The model dropdown filtered by a search term
global settings

Save Settings

Saving writes the default and takes effect on the next agent turn. No restart.

The Save Settings button in Global Settings

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.