Concepts
Milieu is an AI agent ecosystem in which agents perceive and act through command-line interfaces. Much of the design follows from that: a CLI can be allowlisted before it runs and recorded after it runs.
This document defines the entities Milieu is built from, what each one is for, and how they fit together. Command syntax is in REFERENCE; the mechanics of the running system are in ARCHITECTURE. Read this when you are new, or when you have to decide between two things that sound alike — which layer a document belongs in, whether to open a session or send a message.
Everything here describes Milieu as built today. Planned work is in ROADMAP.
Contents
- 1. Where Milieu sits
- 2. The shape of the system
- 3. Agent
- 4. Role, personality, responsibility
- 5. Skill
- 6. Permission, tool, sandbox
- 7. Knowledge
- 8. Memory
- 9. Layers: global, project, agent
- 10. Project
- 11. Run: one turn of work
- 12. Message, route, wake, ask
- 13. Session
- 14. Transcript, audit log, blob
- 15. Trust boundaries: sandbox, API, gateway
- 16. Manifest and the control plane
- 17. How the entities relate
- 18. Best practices
1. Where Milieu sits
Two kinds of AI system are worth telling apart. One is the agent a person already has in front of them — Claude Desktop, Gemini Enterprise, OpenAI, Microsoft Cowork, Amazon Quick — running on a model you did not choose, in an environment you do not administer. The other is the enterprise side, where work touches systems of record, and the hard part is not capability but authority: which credential, reaching which host, on whose behalf, and what the record says afterwards.
Milieu is the enterprise side. It runs as a VM or container of its own, split into a trusted zone that holds the credentials and an untrusted zone where the agents actually work, with a network edge that a user’s own agent can call into.
flowchart LR
ENT[("Enterprise systems<br/>REST · CLI · SQL · MCP servers")]
subgraph HOST["Milieu — VM or container"]
direction LR
subgraph TZ["Trusted zone"]
GW["Gateway broker<br/>secrets · API access<br/>own CA · own audit log"]
end
subgraph UZ["Untrusted zone"]
AG["Agents<br/>sandboxed · allowlisted CLIs<br/>no ambient secrets"]
end
EDGE["API edge<br/>REST · A2A<br/>bearer key over TLS"]
end
LLM1["Model backend<br/>Claude, or any<br/>OpenAI-compatible endpoint"]
UA["User agent<br/>Claude Desktop · Gemini Enterprise<br/>OpenAI · MS Cowork · Amazon Quick"]
LLM2["Its own model<br/>not configured by Milieu"]
ENT <--> GW
GW <-->|mTLS, credential injected here| AG
AG <--> EDGE
EDGE <-->|network| UA
AG -.-> LLM1
UA -.-> LLM2
classDef trusted fill:#e0e7ff,stroke:#6366f1
classDef untrusted fill:#fee2e2,stroke:#ef4444
class GW trusted
class AG untrusted
Each part of that picture is something that ships:
| In the picture | In Milieu | Runs as |
|---|---|---|
| Enterprise systems | Upstreams reached only through a named, operator-declared route | gateway routes: rest, cli, sqlite, postgres, mcp |
| Trusted zone — secrets and API access | The gateway broker: holds the credentials, is its own CA, authorizes each call by client-certificate CN, and keeps its own hash-chained audit log | milieu gateway serve, deployed apart from the host |
| Untrusted zone — AI and agents | Agent sandboxes: a granted CLI allowlist, a fixed PATH, a per-host egress allowlist, no ambient secrets | bwrap on Linux, sbexec on macOS |
| VM or container | One binary over one state tree under $MILIEU_HOME | Containerfile, systemd, or Kubernetes |
| The network edge | REST API and A2A, both authenticated by a bearer key over TLS; A2A publishes a public agent card per agent | milieu api serve, milieu a2a serve |
| LLM model, Claude | A pluggable backend: claude, openrouter for any OpenAI-compatible endpoint including Azure, LiteLLM, vLLM or a local llama-server, and echo for offline runs | MILIEU_LLM |
| The user agent’s model | Outside Milieu entirely | — |
One label belongs on the other edge. MCP is something Milieu consumes, not
something it serves: the gateway has an mcp backend that brokers an
operator-allowlisted tool set from an external MCP server, projected to a skill
as <route>__<tool>, so it sits between the trusted zone and the enterprise
systems. A user’s agent reaching in arrives over REST or A2A instead.
A2A goes both ways, but not symmetrically. Milieu serves it in full and
consumes it one exchange at a time: the gateway’s a2a backend brokers
message/send to a remote peer, so an agent here can put a question to an agent
elsewhere without holding that peer’s credential. What it cannot yet do is
remember the conversation — carrying a peer’s contextId across runs needs
durable gateway state, and is on the
roadmap.
The asymmetry is the point of the layout. Inside its sandbox an agent is comparatively free: it composes whatever granted CLIs it likes, writes its own memory and transcripts, and decides for itself how to reach an answer. The policy lives at the two edges instead — what it may execute and which hosts it may reach on one side, and a credential it never holds on the other. Everything in the rest of this document is either something an agent works with inside that box, or something that defines one of its edges.
2. The shape of the system
Milieu is built around three principles, and most of the entities below serve at least one of them:
- Observability — every action is visible: one audit row per invocation, a transcript per run, durable messages, readable state on disk.
- Explainability — an agent’s answer can be reconstructed from inspectable inputs: its briefs, its skill, and the knowledge and memory layers it read.
- Accountability — every action binds to an identity, and the record is append-only and hash-chained.
mindmap
root((Milieu))
Agent
Identity
code
role
credentials
Personality
personality.md
responsibility.md
Knowledge
axioms
articles
news
events
Skills
SKILL.md
allowed-tools
bundled resources
Work
Run
act
wake
Session
external conversation
turns
Message
outbox
inbox
reply depth
Project
instructions
members
layer
Substrate
Layers
global
project
agent
Records
transcript
audit log
blob store
Boundaries
sandbox
API edge
gateway broker
Control
milieu CLI
milieu daemon
milieu apply
Those concepts run on one binary invoked per command, over one on-disk state tree:
architecture-beta
group edge[Callers]
service op(server)[Operator or CI] in edge
service client(internet)[External program] in edge
group host[Milieu host]
service api(server)[API server] in host
service daemon(server)[Daemon] in host
service agent(server)[Agent run sandboxed] in host
service store(database)[Knowledge and memory] in host
service audit(disk)[Audit log] in host
service blobs(disk)[Blob store] in host
group trusted[Trusted zone]
service gw(server)[Gateway broker] in trusted
service upstream(cloud)[Upstream systems] in trusted
op:R --> L:daemon
client:R --> L:api
api:B --> T:agent
daemon:B --> T:agent
agent:R --> L:store
agent:B --> T:blobs
agent:B --> T:audit
agent:R --> L:gw
gw:R --> L:upstream
Only the servers are resident processes. An agent is a directory; it runs for the duration of one skill invocation and then exits. Most of what follows is therefore a file you can read, diff and back up.
3. Agent
An agent is an identity that can be asked to do work. On disk it is a
directory, var/agents/<code>/: no process, no port, and no memory beyond what
it writes down.
Four facets define one:
| Facet | Concretely | Answers |
|---|---|---|
| Identity | agent.toml — code, role, permissions, network allowlist, gateway identity | Who is this, and what may it do? |
| Personality | personality.md, responsibility.md | How does it behave, and what does it own? |
| Knowledge | its knowledge/ and memory/ layers | What does it know and remember? |
| Skills | skills/<name>.md plus the permissions those tools need | What can it do? |
The facets vary independently: two agents can share a skill and differ in permissions, or share permissions and differ in responsibility. The agent code is what the rest of the system attributes to — audit rows, API keys, gateway certificates, messages and sessions all name one.
4. Role, personality, responsibility
These three are easy to confuse.
- Role is a capability category recorded in
agent.tomland enforced by the code:administrator(may dispatch work to any agent),associate(the default, does the work),assistant(a narrower associate, by convention), andauditor(read-only, and unable to send messages — the one role rule the code enforces today). - Responsibility (
responsibility.md) is prose describing the job within that capability: scope, boundaries, and what “done” looks like. - Personality (
personality.md) covers how the agent communicates: tone, voice, length, demeanour.
Both briefs are prepended to the system prompt on every run, above project instructions and the skill body. The role is checked by the system; the briefs only steer the model. Keep them consistent — an auditor’s brief should not claim it resolves tickets, since it cannot send the message that would resolve one.
5. Skill
A skill is a capability in Anthropic’s Agent Skills format: skills/<name>.md,
or a directory skills/<name>/SKILL.md with bundled resources beside it. YAML
frontmatter declares name, description and optionally model, max-iterations,
allowed-tools, version, license and metadata. The body is the system
prompt.
Skills load progressively. The catalogue an agent sees is names and
descriptions; a run loads one body; bundled resources are read only when the
skill asks for them through the skill tool. act --auto lets the model choose
from the catalogue, and the choice is recorded in the audit log.
Every run records two identifiers for the skill it ran: the version from
frontmatter, and a digest — the SHA-256 of the skill as loaded, covering the
manifest and every bundled resource. A version is a claim the author can forget
to update, so the digest is what tells you which skill answered.
allowed-tools declares tools rather than granting them. It is intersected with
the agent’s permissions, so a skill that asks for curl on an agent that has no
curl grant does not get curl.
6. Permission, tool, sandbox
A permission is a rule attached to an agent. Rules match argument tokens
rather than a flattened string: cmd for any invocation, cmd(a b) for an
exact argv, cmd(a b:*) for a prefix, cmd(:*) for any argv.
A tool is what the model may call during a run, and there are two kinds:
- Sandboxed commands — a real CLI, executed by the runner inside a sandbox
backend (
bwrapon Linux,sbexecon macOS,noneonly if you opt in). Commands resolve against a fixed sandbox PATH rather than the caller’s$PATH, so the binary that was permission-checked is the binary that runs. - Built-ins — handled in-process and audited with sandbox
builtin:knowledgeandskill(read-only),transcript(read-only recall, permission-gated),memory(can write, permission-gated),session(declares that a reply is a question),gateway(a brokered call), andcode(a short Starlark program).
code runs a short Starlark program. Starlark has no standard library, so a
program reaches only the functions milieu injects, and which ones those are is
derived from the agent’s own grants. run() inside a script goes
through the same runner and writes its own audit record. Its purpose is to
collapse a chain of dependent tool calls into a single step, which costs fewer
tokens and less latency than one round trip per element.
Inside the sandbox the agent’s home is writable, protected directories are
read-only, extra host paths appear only through operator-authored [[mount]]
blocks, and network egress is limited to a per-host allowlist. No secrets are
passed in; reaching a credentialed service is what the gateway is for.
7. Knowledge
Knowledge is hand-authored markdown that agents read. It holds the curated part of what an agent knows, filed by how stable it is rather than by topic:
| Kind | Directory | Changes | Example |
|---|---|---|---|
| Axioms | axioms/ | almost never | “Refunds are issued to the original payment method.” |
| Articles | articles/ | on review | a runbook, a policy, a data dictionary |
| News | news/ | often, expires | “Ongoing: East region outage” |
| Events | events/ | append-only | a discrete occurrence or signal |
A file may carry OKF frontmatter — the Open Knowledge Format — with type,
title, tags, status, stale_after, generated, verified and
supersedes. It is optional throughout: a file without frontmatter counts as
stable, unverified and never stale, so an existing base keeps working
untouched.
Two pieces of that metadata keep a base usable as it ages. stale_after marks
time-bound items, so a finished outage notice stops outranking live policy;
stale and non-stable items are labelled rather than hidden in ls, search and
get, including in the tool output the model sees, so an agent reads the caveat
before it answers from the document. And a newer concept retires older ones by
listing their ids under supersedes, which makes “what is still in force” a
query (search --current) rather than something you have to know.
Search runs per layer and is hybrid by default: an FTS5 lexical index, plus a
semantic vector index when an embedder is configured, fused by reciprocal-rank
fusion. Editing a file does not update the index — milieu knowledge reindex
does.
8. Memory
Memory is tool-written notes that an agent keeps. It is layered like
knowledge but authored the other way round: knowledge is edited by people and
then indexed, while memory is written by the agent mid-run through the
permission-gated memory tool, or by milieu memory save.
A memory is one file holding one fact, with a name, a description used to
judge relevance at recall time, and an advisory type (user, feedback,
project, reference). Each layer keeps an auto-maintained MEMORY.md index.
The index reaches every run: it is prepended to the system prompt, so an agent
always knows what it has notes about. The bodies cost something to read, so they
are fetched on demand with memory(search:*) and memory(get:*), and
memory(save:*) records something new.
Use knowledge for what the organisation asserts and memory for what one agent learned. If a person should review it before an agent repeats it, it belongs in knowledge.
9. Layers: global, project, agent
Knowledge and memory are each scoped in three layers:
block-beta
columns 3
A["agent — most specific\nvar/agents/<code>/ · ungated"]:3
B["project — membership-gated\nvar/projects/<code>/ · only for members"]:3
C["global — shared by everyone\nvar/global/ · always included"]:3
space:3
D["--as <code> adds the agent layer"] E["--in <project> adds the project layer"] F["global is always in the union"]
style A fill:#dbeafe,stroke:#3b82f6
style B fill:#e0e7ff,stroke:#6366f1
style C fill:#ede9fe,stroke:#8b5cf6
A read applies two rules:
- Union — every applicable layer is merged, and
searchmerges results across them. - Shadow — on a path or name clash the more specific layer wins
(agent > project > global), and
getreturns the most-specific match.
A global articles/refund-policy.md is the house default; a project can shadow
it with its own, and an agent can shadow that again. Nothing is copied — the
specific file hides the general one for that reader, and stops hiding it if you
delete it.
Each layer has its own index, so reindex --scope agent|project|global rebuilds
one layer rather than all of them.
10. Project
A project is a named workspace with three parts:
- Instructions (
instructions.md), prepended to the skill body on--inruns, holding the standing context for this piece of work. - A member roster that gates who may act in the project and who may read its knowledge and memory. A non-member gets a refusal rather than an empty result.
- A project layer of knowledge and memory, between the agent and global layers.
Projects scope work without cloning agents. One support agent run --in acme
reads ACME’s clauses and follows ACME’s instructions; the same agent run without
--in sees only its own layer and global.
11. Run: one turn of work
A run is one skill executed as one agent: input in, reply out, transcript
written, audit row appended. Every entry point — milieu act, the REST API run
routes, the A2A endpoint, and the daemon’s wake — goes through the same code,
so a turn is scoped, executed and recorded the same way whichever door the
caller came in by.
sequenceDiagram
autonumber
actor Caller
participant Act as act (one turn)
participant Ctx as Context assembly
participant LLM as Model
participant Run as Runner + sandbox
participant Rec as Transcript + audit
Caller->>Act: input, --as mira, --in acme, skill
Act->>Act: resolve skill · check project membership
Act->>Ctx: briefs + project instructions + MEMORY.md index + skill body
Ctx-->>LLM: system prompt + replayed session turns + input
loop while the model calls tools
LLM->>Run: tool_use (allowed-tools ∩ permissions)
Run->>Run: sandbox or built-in
Run-->>Rec: one audit row per call
Run-->>LLM: tool result
end
LLM-->>Act: reply
Act->>Rec: transcript (prompt, turns, tools, tokens, skill digest)
Act-->>Caller: reply
A run’s context is fixed and inspectable: briefs, project instructions, memory index, knowledge layers, skill body. Each of those is a file on disk, which is what makes an answer reconstructible weeks later.
12. Message, route, wake, ask
Agents collaborate by message rather than by calling each other. Each agent has
two SQLite databases in its home — out.db for what it has sent and in.db for
what has been delivered to it — and three verbs move a message between them:
- send writes a row to the sender’s outbox.
- route copies undelivered outbox rows into recipients’ inboxes.
- wake drains an inbox, runs a skill per unread message, and writes the reply to the outbox.
milieu daemon runs the last two on a loop.
sequenceDiagram
autonumber
participant Arun as arun (administrator)
participant Out as arun/out.db
participant D as daemon (route · wake)
participant In as mira/in.db
participant Mira as mira (associate)
Arun->>Out: send "triage ticket 4821" (+ blob digest)
D->>Out: poll undelivered
D->>In: deliver (reply_depth 0)
D->>Mira: wake — run respond on the unread message
Mira->>Mira: knowledge · memory · tools
Mira-->>In: mark consumed only after the reply is sent
Mira->>D: reply (reply_depth 1)
D->>Arun: route back
Note over D,Mira: reply_depth caps reply chains<br/>at MaxReplyDepth, which is 5
A message is marked consumed only after its reply has been sent, so a crashed
wake re-processes it instead of dropping it. Every message carries a
reply_depth that wake increments on reply and stops at 5, which ends what
would otherwise be an endless exchange between two agents.
ask is the synchronous form over the same machinery: it sends a message
stamped with a random correlation id and polls the caller’s inbox for the
matching Re: reply. The ask: subject family is reserved so the daemon never
auto-replies to a reply.
Attachments travel by reference. The bytes go once into the content-addressed
blob store, the message carries a digest and media type, and wake hands them
to the recipient’s skill as image or document blocks.
The same flow as an event model — what triggers what, what gets written, and what can be read back afterwards:
flowchart LR
subgraph T["Trigger"]
OP["operator, API client,<br/>or another agent"]
end
subgraph C["Command"]
SEND["send"]:::cmd
ROUTE["route"]:::cmd
WAKE["wake"]:::cmd
end
subgraph E["Durable record"]
OUTBOX[("out.db row")]:::evt
INBOX[("in.db row")]:::evt
TR[("transcript")]:::evt
AUD[("audit row")]:::evt
end
subgraph R["Read model"]
IB["message inbox"]:::rm
REP["audit report"]:::rm
RECALL["transcript recall"]:::rm
end
OP --> SEND --> OUTBOX --> ROUTE --> INBOX --> WAKE
WAKE --> TR
WAKE --> AUD
INBOX --> IB
AUD --> REP
TR --> RECALL
WAKE -->|reply| OUTBOX
classDef cmd fill:#dbeafe,stroke:#3b82f6
classDef evt fill:#dcfce7,stroke:#22c55e
classDef rm fill:#fef3c7,stroke:#f59e0b
13. Session
A session is the durable record of one external conversation: the turns a client sent, the replies it received, and enough identity to pick the conversation up later. Without one, every run starts cold.
stateDiagram-v2
[*] --> open: session open --topic billing
open --> open: client turn → agent turn
open --> input_required: agent turn declares<br/>session request-input
input_required --> open: the client answers
open --> closed: session close
input_required --> closed: session close
closed --> [*]: still readable, refuses new turns
note right of input_required
State is derived from the turns
rather than stored.
end note
Sessions live in var/sessions/, outside the sandbox home, so an agent cannot
write its own. An agent that could edit its history could rewrite its next
prompt; because it cannot, resuming from a session is safe.
The state in the diagram is derived from the turns rather than stored: closed
once the client closes it, input-required while the newest turn is an agent
turn that declared it was asking a question, open otherwise. The client’s next
message clears input-required, so there is no flag to reset and none that can
drift from the turns.
A session pins one topic and one agent. A client talking to three agents opens three sessions, and delegation between those agents stays internal.
A conversation outlives a context window, so a resumed run replays the most
recent turns that fit a byte budget, always starting at a client turn, and tells
the model when its view is only a suffix. milieu compact folds the older turns
into a summary instead of dropping them. The summary is a derived file beside
the turns and no turn is ever rewritten, so session show keeps printing the
whole conversation. Each pass costs a model call, which is why it is a command
rather than something that happens on its own.
Sessions record conversations with something outside Milieu. Agents talking to
each other is internal state: messages and transcripts, bounded by reply_depth.
14. Transcript, audit log, blob
Three records with three different jobs:
| Transcript | Session | Audit log | |
|---|---|---|---|
| Records | how the agent produced a reply — system prompt, turns, tool calls, tokens | what the client said and heard | that an invocation happened, with metadata |
| Lives in | var/agents/<code>/state/<topic>/ | var/sessions/<id>/ | var/audit/audit.jsonl |
| Agent may write it | yes — it is the agent’s own working memory | no | no, appended by the system |
| Tamper-evident | no | no | yes — SHA-256 hash chain |
| Fed into later runs | recalled by index, read on demand | replayed | never |
| Retention | swept by cleanup transcript | kept until swept explicitly | append-only, kept |
The audit log holds one JSON row per CLI invocation and per built-in tool call,
with seq, agent, cmd, args, exit_code, timings, sandbox backend, and
the chain fields prev_hash and hash. Editing, reordering or deleting any
record breaks every following hash, and milieu audit verify reports the first
break. Rows for calls that reached a model carry the tokens they spent, so
audit report attributes spend per agent; cost is computed at report time from
a price table rather than frozen into the log.
The chain is unkeyed, which bounds what it proves. It detects tampering by anyone who cannot rewrite the file, but an operator with write access can recompute the whole chain, and lopping records off the end leaves a shorter chain that still verifies. Anchor the latest hash somewhere external if that matters to you.
The blob store (var/blobs/) is content-addressed by SHA-256 and shared across
agents, so identical attachments are stored once and referenced by digest. That
is how an attachment rides a message between agents without the bytes being
copied.
15. Trust boundaries: sandbox, API, gateway
Milieu has three trust boundaries, each with its own identity mechanism and its own log, and they are separate trust domains by design.
sequenceDiagram
autonumber
participant Ext as External program
participant API as API server
participant Agent as Agent sandbox
participant Proxy as Local proxy
participant GW as Gateway broker
participant Up as Upstream
Ext->>API: bearer key sk_… over TLS
Note over API: key is stored only as a SHA-256 hash,<br/>it resolves to exactly one agent
API->>Agent: run a skill as that agent
Note over Agent: least privilege — CLI allowlist,<br/>fixed PATH, host allowlist,<br/>no ambient secrets
Agent->>Proxy: gateway tool call, loopback only
Proxy->>GW: mutual TLS, Ed25519
Note over GW: authorizes by client-cert CN,<br/>holds the credential
GW->>Up: request with the injected credential
Up-->>GW: response
GW-->>Agent: response — the secret never entered the sandbox
- Sandbox boundary. The agent runs with least privilege and no ambient secrets; everything it may execute was granted explicitly.
- API edge. External programs drive Milieu as an agent over one-way TLS, authenticated by a bearer key that is stored only as its hash and can be revoked. The key is interaction-only: no agent or project management, no command execution. A caller sees only its own sessions, and another agent’s id returns 404 rather than 403, so probing reveals nothing.
- Gateway. A broker in a separate trusted zone holds the secrets. The agent talks to a loopback proxy, the proxy re-originates over mutual TLS, and the gateway authorizes the call by client-certificate CN, injects the credential, and logs the call — never the secret — to its own hash-chained log. Routes are REST, CLI, SQL named queries, or a brokered MCP server.
Credentials stay in the gateway, identity is carried by the certificate, and the sandbox holds neither.
16. Manifest and the control plane
There are three control surfaces over the same services:
milieu setupis a guided first run: it asks a few questions, writesmilieu.yaml, and applies it.milieu <cmd>is the imperative CLI, for exploration and one-off work.milieu apply -f milieu.yamlis the declarative control plane, the kubectl analog. It takes a multi-document YAML manifest (apiVersion: milieu.dev/v1) ofAgent,Project,Knowledge,Gateway,API,WebandSettingsdocuments and reconciles them by calling the same services the CLI does.
Reconcile is upsert with no prune: declared resources are set to match the
manifest, and resources absent from it are left alone. Secrets stay out of the
manifest — apply mints API keys and prints them once.
journey
title An operator's first hour
section Install
make build: 5: Operator
milieu setup — answer a few questions: 4: Operator
Review the generated milieu.yaml: 4: Operator
section Shape the team
Edit personality.md and responsibility.md: 3: Operator
Write the first skill: 3: Operator
Grant only the permissions it needs: 4: Operator
section Give it something to know
Seed global knowledge: 4: Operator
knowledge reindex: 5: Operator
Create a project and add members: 4: Operator
section Put it to work
act — one turn, read the transcript: 5: Operator
ask — one agent delegates to another: 4: Operator, Agent
Start the daemon: 5: Operator
section Keep it honest
audit report — who spent what: 5: Operator
audit verify — the chain is intact: 5: Operator, Auditor
Re-apply the manifest on every change: 4: Operator
Explore with the CLI, then keep the result in the manifest. Anything that should survive a rebuild belongs there, and in version control.
17. How the entities relate
erDiagram
AGENT ||--o{ SKILL : "carries"
AGENT ||--o{ PERMISSION : "is granted"
AGENT ||--|| ROLE : "has"
AGENT ||--|| BRIEFS : "prepends"
AGENT ||--o{ KNOWLEDGE_LAYER : "owns"
AGENT ||--o{ MEMORY_LAYER : "owns"
AGENT }o--o{ PROJECT : "is member of"
PROJECT ||--|| KNOWLEDGE_LAYER : "owns"
PROJECT ||--|| MEMORY_LAYER : "owns"
PROJECT ||--|| INSTRUCTIONS : "prepends"
GLOBAL ||--|| KNOWLEDGE_LAYER : "owns"
GLOBAL ||--|| MEMORY_LAYER : "owns"
AGENT ||--o{ RUN : "performs"
SKILL ||--o{ RUN : "is executed by"
RUN ||--|| TRANSCRIPT : "writes"
RUN ||--o{ AUDIT_ROW : "appends"
RUN }o--o| SESSION : "continues"
SESSION ||--o{ TURN : "holds"
AGENT ||--o{ MESSAGE : "sends and receives"
MESSAGE }o--o{ BLOB : "references by digest"
RUN ||--o{ TOOL_CALL : "makes"
TOOL_CALL }o--|| PERMISSION : "is gated by"
TOOL_CALL }o--o| GATEWAY_ROUTE : "may broker through"
KNOWLEDGE_LAYER ||--|| INDEX : "is searched via"
MEMORY_LAYER ||--|| MEMORY_INDEX : "is summarised by"
The diagram falls into three groups: who the agent is (role, briefs, permissions), what it can read (layers, indexes, projects), and what happened (run, transcript, audit, session, message).
18. Best practices
Agents and roles
- Split agents by responsibility, not by skill count. Two narrow agents that message each other are easier to audit than one agent holding twelve permissions.
- Keep the briefs short and specific. “You own first contact. You classify and route — you do not resolve.” works better than a paragraph of adjectives.
- Give a team an auditor. It cannot send messages, so it cannot be drafted into the work it reviews.
- Do not write a brief that contradicts the role. The role wins, and the agent looks broken.
Skills
- Write the description for the selector. Under
--autoit is all the model has to choose from. - Declare
allowed-toolsnarrowly. It is intersected with grants anyway, and a tight declaration documents intent. - Push detail into bundled resources. Keep the body about how to decide, and let reference files carry the tables and examples that are loaded only when needed.
- Have the skill say when it is stuck. If it cannot finish without something only
the client can supply,
session request-inputmarks the turn, and the conversation shows up as outstanding instead of sitting in a queue looking answered. - Compare digests, not versions, when behaviour changes unexpectedly between runs.
Permissions and the sandbox
- Grant the narrowest rule that works:
curl(https://api.example.com:*)rather thancurl, and an exact rule when the command line is fixed. - Keep
MILIEU_SANDBOX=noneto throwaway environments. It is opt-in and never auto-selected for a reason. - Use
codewhen a task is a loop. One Starlark program over N items costs far less than N tool round trips, and it cannot exceed the agent’s grants. - Do not pass secrets into the sandbox. An agent that needs a credential needs a gateway route.
Knowledge
- File by stability, not topic. Something that changes weekly is news, not an article; something that will still hold next year may be an axiom.
- Put
stale_afteron anything time-bound, and use--freshor--currentin the skills that read it. Unbounded news is how a base rots. - Supersede rather than delete. Listing the retired ids keeps the history answerable and the current answer unambiguous.
- Reindex whenever content changes — the index does not follow the file. Wire it into whatever step publishes the content.
- Have a person verify what matters. A
human:<id>entry underverifiedis what makes an item human-reviewed, and--verifiedis what lets a skill insist on it.
Memory
- One fact per file, with a description worth matching against. The description is what the index shows and what relevance is judged on.
- Grant
memory(save:*)with intent. An agent that writes memory is editing its own future prompts. - Do not put in memory what belongs in knowledge. If other agents need it, or a person should approve it, author it as knowledge.
Layers and projects
- Treat global as the house default and shadow it where you must. Copying a global article into ten agent layers guarantees nine of them go stale.
- Create a project as soon as a second agent needs the same context. Instructions plus roster plus layer is the bundle you were about to assemble by hand.
- Keep membership tight. The roster gates project knowledge and memory, not just who may act.
- Reindex per scope:
--scope projectafter seeding a project layer, and leave global alone.
Sessions and conversations
- Open a session for anything a client will continue.
- Let the client supply the id when it has one. The format matches the A2A
contextIdshape, so ids line up across systems. - Schedule
compactfor long conversations rather than letting the replay budget quietly drop the beginning. - Sweep idle sessions explicitly. Bare
cleanupnever touches them.
Messaging
- Use
askwhen you need the answer now, andsendplus the daemon when you do not. - Design within
reply_depth. Chains cap at 5, and a workflow needing more hops wants a different shape. - Let attachments ride by digest rather than inlining bytes into a message body.
Operations
- Keep the manifest in version control and re-apply on every change. Upsert with no prune means the manifest is a floor rather than a fence, so pair it with review.
- Run
audit verifyon a schedule, and anchor the latest hash somewhere the operator of the host cannot rewrite. - Read
audit reportper agent rather than in aggregate. Spend per agent is the earliest sign that a skill has started looping. - Never put
var/on NFS. SQLite WAL needs proper POSIX locks. - Back up one path: everything lives under
$MILIEU_HOME.
Where to next
- GETTING-STARTED — stand this up end to end.
- USER-GUIDE — the same concepts with commands attached.
- ARCHITECTURE — how it is built: processes, state, boundaries.
- SECURITY — the trust model and hardening checklist.
- REFERENCE — grammar, schemas, env vars, glossary.