Architecture
Alfred is shaped by three constraints: a harness-agnostic core, deny-by-default security, and evals-as-gate. This page explains why it is built the way it is, and what that means for your agents and skills.
Hexagonal architecture
The codebase is split into a core and a set of adapters. The rule is enforced by the build, not by convention:
packages/coreimports nothing frompackages/*-adapter.- Adapters depend on core. They never redefine domain rules.
- The boundary is checked by
scripts/validate-policies.mjsin CI.
Domain-driven design
The domain lives in .ai/domain/model.md. Entities,
value objects, use cases, and ports are written in plain markdown
so the agents themselves can read them.
The vocabulary is fixed:
- Agent — a role with a mode
(
primaryorsubagent). - Skill — lazy-loaded instructions keyed by trigger.
- Harness — the runtime that hosts the agent (Pi, opencode, Codex, ...).
- PermissionPolicy — deny-by-default, protected paths, destructive commands.
- TraceEvent — the atomic unit of observability.
- EvalCase / Baseline — the regression gate.
- TemporaryAgent — a subagent with a lifecycle, never promoted without human approval.
The architecture kernel
Every agent reads the same kernel file at the top of its prompt. The kernel pins the rules the LLM cannot negotiate:
# Alfred kernel (excerpt) 1. Local-first. Deterministic local work before any provider call. 2. Deny by default. Protected paths require human approval. 3. No self-escalation. Agents do not broaden their own permissions. 4. Trace everything. Emit a TraceEvent per operation. 5. Evals gate everything. Run regression suite before declaring done. 6. Skills are lazy. Activate by trigger, never by file presence. 7. Specialists are temporary. Promotion requires a human.
Package layout
| Package | Role | Imports from core |
|---|---|---|
packages/core |
Domain, use cases, ports, policies. Harness-agnostic. | — |
packages/pi-adapter |
First-class Pi runtime. | yes |
packages/opencode-adapter |
opencode translation spike. | yes |
packages/codex-adapter |
Codex custom agents + repo skills. | yes |
packages/memory-server |
Vendor-agnostic memory API (SQLite or Postgres). | yes |
packages/evals |
Eval runner, baselines, reports. | yes |
packages/console-web |
Web console for tenants, keys, and copy-paste snippets. | via memory-client |
Everything is a trace
Every meaningful operation writes a TraceEvent into
.ai/observability/generated/. The trace log is the
source of truth for debugging, replaying, and reviewing
agent behavior.
{"ts":"2026-06-30T18:02:11Z","kind":"provider.request","agent":"developer","decision":"local-only","avoided_cost_usd":0.002}
{"ts":"2026-06-30T18:02:14Z","kind":"permission.request","path":".ai/policies/security.md","decision":"deny","actor":"human"}
{"ts":"2026-06-30T18:02:18Z","kind":"eval.run","suite":"default","passed":14,"failed":0,"vs_baseline":"regression-safe"}
Local-first in practice
Before any provider call, Alfred's
ProviderRequestPolicy runs a deterministic checklist:
- Can a local capability answer this?
- Can preprocessing reduce the payload?
- What is the estimated token / cost impact?
- Emit
local-only,hybrid, orprovider. - Trace the decision, including avoided cost.
Security, summarized
- Deny by default. Allowlists are explicit and minimal.
- Protected paths (
.ai/,.opencode/,harnesses/) require human approval. - Destructive commands (
rm -rf,git reset --hard, ...) require human approval. - Skills cannot override security policy. Only a human can.
- Adapters enforce policy. They do not own it.