Getting Started
This guide walks through standing up Milieu for a realistic scenario: a contact centre for a broadband provider. A frontline agent answers customer conversations using a knowledge base of policies and runbooks, remembers past conversations, and is overseen by a supervisor and an auditor.
By the end you will have:
- A running Milieu environment with three agents in distinct roles.
- A knowledge base ingested and searchable (lexically, optionally semantically).
- Tools (permissions + skills) so the frontline agent can consult the knowledge base while answering.
- Conversation memory — past conversations saved as knowledge events and recalled in later answers.
- Three ways to respond to new conversations: one-off, synchronous request/reply, and a fully autonomous daemon.
Everything below is copy-pasteable, in order. To watch it run first, make demo
replays this whole guide as a script against
examples/minimal/, echoing each command before it runs.
Once you’ve finished, the
USER-GUIDE covers each feature in depth, OPERATIONS
covers running the servers and stack, and REFERENCE lists every
command and flag.
Contents
- Build and initialise
- Create the agents
- Ingest the knowledge base
- Set up tools
- First conversation
- Save conversation memory
- Respond to new conversations
- Audit the operation
- Going further: per-client projects
- Where to go next
1. Build and initialise
Prerequisites: Go 1.26+, and a sandbox backend — sandbox-exec is built into
macOS; on Linux install bwrap.
git clone <repo> && cd milieu
make build # produces ./milieuThe fast path. ./milieu setup asks the questions this section and the next
three answer by hand — sandbox, model, embeddings, a roster of agents, a
knowledge base, and which servers to stand up — then writes them to
milieu.yaml and applies it. Choosing the contact centre starter gets you to
the end of step 4 in one command, on the same three agents and the same
knowledge files used below. Everything after that (skills, memory,
conversations, the daemon, the audit log) still runs as written.
The rest of this guide does it the long way, because seeing each piece created individually is what makes the manifest legible afterwards.
export MILIEU_HOME=$PWD/.milieu # all state lives here
export ANTHROPIC_API_KEY=sk-ant-... # enables LLM-driven skills (claude backend)
# The sandbox auto-resolves per platform (sandbox-exec on macOS, bwrap on
# Linux); set MILIEU_SANDBOX only to override.To use OpenRouter (or any OpenAI-compatible gateway) instead of Anthropic, set
MILIEU_LLM=openrouter with OPENROUTER_API_KEY and pick a model via
MILIEU_LLM_MODEL (default anthropic/claude-haiku-4.5); see REFERENCE.
Without any LLM credentials, skills fall back to an echo backend — useful
for plumbing checks, but the contact-centre answers below need a real model.
2. Create the agents
A contact centre has roles. Milieu agents do too:
- mira — frontline associate. Does the work: answers conversations.
- arun — administrator. May dispatch work to any agent.
- vera — auditor. Read-only; reviews the audit log, cannot send messages.
./milieu agent create mira # default role: associate
./milieu agent create arun --role administrator
./milieu agent create vera --role auditor
./milieu agent lsEach agent gets a directory under $MILIEU_HOME/var/agents/<code>/ holding
its identity (agent.toml), personality and responsibility briefs
(personality.md, responsibility.md), skills, and message queues.
3. Ingest the knowledge base
Knowledge is hand-authored markdown, layered by stability: axioms/
(slow-changing truths), articles/ (curated docs), news/ (time-sensitive),
events/ (discrete occurrences). It is scoped in three layers, all under
var/: the global base at var/global/knowledge/ (shared by everyone),
each agent’s own base at var/agents/<code>/knowledge/, and per-project bases
at var/projects/<code>/knowledge/ (membership-gated). Reads union the
applicable layers most-specific-first — agent over project over global — with a
more-specific file shadowing a lower one on a name clash.
Seed the global base with the material a contact centre runs on — tone policy, a refund policy, a troubleshooting runbook, and a current outage notice:
mkdir -p .milieu/var/global/knowledge/{axioms,articles,news,events}
cat > .milieu/var/global/knowledge/axioms/tone.md <<'EOF'
# Tone of voice
Always be courteous and concise. Apologise once for any inconvenience,
then move to the resolution. Never promise credits beyond policy.
EOF
cat > .milieu/var/global/knowledge/articles/refund-policy.md <<'EOF'
# Refund policy
Customers are entitled to a pro-rata service credit for any outage
longer than 4 consecutive hours. Credits are applied to the next
invoice, not refunded to card. Requests older than 60 days are not
eligible.
EOF
cat > .milieu/var/global/knowledge/articles/router-troubleshooting.md <<'EOF'
# Router troubleshooting runbook
1. Ask the customer to power-cycle the router (off 30 seconds, then on).
2. Check the line status in the diagnostics portal.
3. If the DSL light stays red after a power-cycle, book an engineer
visit; the earliest slot is usually within 48 hours.
EOF
cat > .milieu/var/global/knowledge/news/outage-east-region.md <<'EOF'
---
type: news
title: "Ongoing: East region outage"
tags: [outage, credit]
stale_after: 2026-06-15T18:00:00Z
---
# Ongoing: East region outage
Since 06:00 today there is a fibre cut affecting the East region.
Estimated restoration is 18:00. Affected customers qualify for the
standard outage credit.
EOFThat last file carries OKF
frontmatter — the Open Knowledge Format: still markdown, with a little YAML
metadata. Frontmatter is optional (the three files above have none and work
fine), but stale_after earns its keep here: once the restoration time passes,
this notice is flagged everywhere it appears and --fresh drops it, so a
finished outage stops outranking live policy. USER-GUIDE → Metadata
covers the rest of the vocabulary.
Index it, then verify search works:
./milieu knowledge reindex # "indexed 4 item(s) in the global layer (lexical)"
./milieu knowledge ls # browse by layer
./milieu knowledge search "outage credit" # hybrid search (falls back to lexical)
./milieu knowledge search --fresh "outage credit" # same, minus anything past its stale_after
./milieu knowledge get articles/refund-policy.mdRerun ./milieu knowledge reindex whenever knowledge files change.
Optional: semantic search
Lexical (FTS5) search matches terms; semantic search matches meaning, so
“my internet is down” finds the outage notice even though no words overlap.
Embedding runs out-of-process — milieu calls an OpenAI-compatible endpoint
(a local llama-server shown here, or Voyage; see
OPERATIONS → Semantic search):
llama-server -m nomic-embed-text.gguf --embeddings --port 8080 &
export MILIEU_EMBED=openai MILIEU_EMBED_URL=http://localhost:8080/v1
./milieu knowledge reindex # now says "lexical + semantic"
./milieu knowledge search --sem "my internet is down"4. Set up tools
Agents may only invoke what they have been granted. Mira needs the built-in
read-only knowledge tool so her skills can search and read the knowledge base,
and the memory tool to recall and save past conversations. Both need search
and get: search returns a snippet, and she needs the whole document (or the
whole prior conversation) before she can answer from it. session lets a reply say
she needs something back from the customer before she can finish.
./milieu permission grant mira 'knowledge(search:*)'
./milieu permission grant mira 'knowledge(get:*)'
./milieu permission grant mira 'memory(search:*)'
./milieu permission grant mira 'memory(get:*)'
./milieu permission grant mira 'memory(save:*)'
./milieu permission --as mira lsNow give mira a respond skill. A skill follows Anthropic’s Agent Skills
format: a directory holding a SKILL.md manifest (YAML frontmatter + body)
and, optionally, bundled resource files the model reads on demand. A flat
skills/<name>.md file works too for quick one-offs. allowed-tools
exposes granted CLIs to the model as callable tools. The name respond
matters: it is the default skill invoked by milieu wake and the daemon
when handling incoming messages.
mkdir -p .milieu/var/agents/mira/skills/respond/references
cat > .milieu/var/agents/mira/skills/respond/SKILL.md <<'EOF'
---
name: respond
description: Answer a customer conversation using the knowledge base
version: 1.0.0
metadata:
author: ops-team
allowed-tools:
- knowledge(search:*)
- knowledge(get:*)
- memory(search:*)
- memory(get:*)
- memory(save:*)
- session(request-input:*)
---
You are a frontline contact-centre agent. You receive one customer
message (possibly with a From/Subject header).
1. Search the knowledge base for the customer's issue
(knowledge, args=["search", "--current", "<keywords>"]). Try a couple of
phrasings. --current returns only what is still in force: it drops policies
that are stale, deprecated, or superseded by a newer one, so you cannot
answer from a retired rule.
2. Read promising hits in full (knowledge, args=["get", "<path>"]).
3. Check for prior conversations with this customer
(memory, args=["search", "<customer name or account>"]), read any hit in
full (memory, args=["get", "<name>"]), and acknowledge relevant history.
4. Reply in 2-4 courteous sentences following the tone axioms, citing
policy where relevant. If the knowledge base has no answer, follow
references/escalation.md — do not invent policy.
5. If you cannot finish without something only the customer can give you —
an order number, a date, a choice between options — say so first
(session, args=["request-input", "<what you need>"]) and then ask for it.
That marks the conversation as waiting on them, so it shows up as
outstanding rather than looking answered.
EOF
cat > .milieu/var/agents/mira/skills/respond/references/escalation.md <<'EOF'
# When to escalate
Escalate when the request falls outside documented policy, the customer
threatens legal action, or there is a safety concern. Say you are
escalating to a supervisor; do not promise an outcome or a timeframe.
EOF
./milieu skill --as mira ls # shows: respond@1.0.0The bundled references/escalation.md is progressively disclosed: the
system prompt lists only its path, and mira reads it mid-conversation via
the built-in read-only skill tool when she actually needs it — no
permission grant required, every read audited.
Validate the whole configuration — TOML, frontmatter, declared-but-ungranted tools, missing files:
./milieu lint mira5. First conversation
Skills read their input from stdin. Handle a customer directly:
echo "Hi, my internet has been down in the East region since this morning. \
Do I get money back?" | ./milieu act --as mira respondMira searches the knowledge base, finds the outage notice and the refund
policy, and answers with the pro-rata credit terms — every knowledge call she
made is in the audit log (section 8).
6. Save conversation memory
Memory is a separate store, scoped in the same three layers as knowledge:
the global memory at var/global/memory/, each agent’s own at
var/agents/<code>/memory/, and per-project memory at
var/projects/<code>/memory/ (membership-gated). Reads union the layers
most-specific-first; writes target a layer by context — --in <project>
writes the project layer, --global writes the global layer, otherwise the
agent’s own. Save the conversation into mira’s own memory:
./milieu memory --as mira save priya-sharma <<'EOF'
# Conversation: Priya Sharma (account 48213) — 2026-06-09
Customer reported no connectivity in the East region since morning.
Confirmed the ongoing fibre-cut outage (ETA 18:00) and that the account
qualifies for the standard pro-rata outage credit on the next invoice.
Customer satisfied; no engineer visit needed. Follow-up: verify the
credit appears on the July invoice.
EOFIn practice the memory tool writes this from inside the skill after each
conversation. Now memory works: when the same customer returns, mira recalls
the history (step 3 of her skill):
echo "This is Priya Sharma again, account 48213. You promised me a credit \
last week — where is it?" | ./milieu act --as mira respondMira finds the saved memory, acknowledges the prior conversation, and answers from the refund policy (credits land on the next invoice) instead of treating Priya as a stranger.
7. Respond to new conversations
Synchronous: ask and wait for the reply
milieu ask sends a message and blocks until the matching reply arrives —
handy for wiring Milieu into a chat widget or shell pipeline. It needs the
daemon (below) running, or manual routing:
# manual routing variant (no daemon): arun forwards a conversation to mira
MILIEU_AGENT=arun ./milieu message send mira "ticket-1042" \
<<< "Customer says the DSL light on the router is red. What should they do?"
./milieu route # deliver outbound -> inbound
./milieu wake --as mira # mira runs 'respond' on her inbox
./milieu route # deliver mira's reply back
MILIEU_AGENT=arun ./milieu message inbox
MILIEU_AGENT=arun ./milieu message read 1Autonomous: run the daemon
milieu daemon polls every agent’s inbox, runs respond on each unread message,
and routes the replies — no manual route/wake:
./milieu daemon --interval 1s &Tip — run the whole stack at once. For dev/demo you can bring up the embedding server, REST API, and router together with process-compose:
process-compose up. Add the trusted-zone gateway withprocess-compose up -f process-compose.yaml -f process-compose.gateway.yaml. Seeprocess-compose.yamland OPERATIONS → Running the stack.
With the daemon up, the whole loop collapses to one blocking call:
echo "Customer on chat: internet down in East region, asking about \
compensation." | ./milieu ask --as arun --to mira --timeout 60sThe reply body prints to stdout (exit 0 on reply, 124 on timeout), so it
composes with anything:
echo "Router DSL light is red after a restart." \
| ./milieu ask --as arun --to mira | tee /tmp/reply.txtReplies carry a reply_depth capped at 5, so two agents can’t fall into an
infinite reply loop. Stop the daemon with kill %1 (it exits cleanly on
SIGINT/SIGTERM).
Let the model pick the skill
So far you named the skill yourself. Give mira a second one and she can route incoming work herself — selection sees only each skill’s name and description, so write descriptions like routing rules:
cat > .milieu/var/agents/mira/skills/handover.md <<'EOF'
---
name: handover
description: Summarise conversation logs into a shift-handover briefing
---
Summarise the conversations below for the incoming shift in five
bullet points: open issues first, then resolved ones.
EOF
# one-off: --auto instead of a skill name
./milieu memory --as mira get priya-sharma \
| ./milieu act --as mira --auto # stderr: auto-selected skill "handover"
# manual wake, dispatching per message
./milieu wake --as mira --auto
# autonomous: the daemon dispatches every message to the best-fitting skill
kill %1 && ./milieu daemon --interval 1s --auto &In wake and the daemon, a message no skill matches falls back to respond
(with a warning) so nothing is left stranded; a plain act --auto errors
instead. Every selection is recorded in the audit log, so vera can review
not just what mira answered but why a given skill handled it.
8. Audit the operation
Every CLI invocation and every tool call mira made — including the built-in
knowledge lookups inside her skill — is one JSON row in the append-only audit
log, with agent, command, args, exit code, and timestamps:
tail -n 10 .milieu/var/audit/audit.jsonlThis is vera’s domain: as an auditor she is read-only and cannot send messages, which makes her safe to grant broad inspection access without her participating in conversations.
9. Going further: per-client projects
If the contact centre serves multiple clients, give each one a project —
a scoped workspace with its own instructions, member roster, and private
knowledge and memory. A project’s knowledge and memory layers sit above the
global base, so a client’s specific SLAs override the general policy. Create
the project and enrol its members with milieu project:
cat > /tmp/acme-instructions.md <<'EOF'
ACME is a premium client. Address the caller as "ACME support".
ACME's contractual outage credit threshold is 1 hour, not 4.
EOF
./milieu project create acme --name "ACME Corp support desk" \
--instructions /tmp/acme-instructions.md
./milieu project member add acme mira
./milieu project member add acme arun
./milieu project ls
./milieu project show acmeproject create lays down var/projects/acme/project.toml (code, name,
description, instructions, members) plus the project’s knowledge/ and
memory/ trees. Seed the project knowledge base and index that one layer:
mkdir -p .milieu/var/projects/acme/knowledge/articles
cat > .milieu/var/projects/acme/knowledge/articles/acme-sla.md <<'EOF'
# ACME SLA
Outages over 1 hour qualify for credit. Engineer visits within 24 hours.
EOF
./milieu knowledge --as mira --in acme reindex --scope project
echo "ACME caller: down for 2 hours, do we get credit?" \
| ./milieu act --as mira --in acme respondMembership gates both the project’s knowledge and its memory: only members may
act in a project. The project instructions are prepended to the skill, and the
knowledge and memory tools inside the skill union ACME’s layer over the
global base (agent over project over global) — so mira correctly applies the
1-hour threshold.
Quick reference
./milieu agent create <code> [--role <role>] # create an agent
./milieu project create <code> [--name <n>] # create a scoped workspace
./milieu project member add <code> <agent> # enrol an agent in a project
./milieu permission grant <agent> <rule> # allow a CLI pattern
./milieu knowledge [--as <a>] [--in <p>] reindex # rebuild a knowledge layer
./milieu knowledge [--as <a>] [--in <p>] search <query> # search unioned layers
./milieu memory --as <a> [--in <p>|--global] save <name> # write a memory layer
./milieu act --as <agent> [--in <proj>] <skill> # run a skill (stdin -> reply)
./milieu act --as <agent> --auto # let the model pick the skill
./milieu ask --as <a> --to <b> # send, block for the reply
./milieu daemon --interval 1s [--auto] # autonomous message handling
./milieu lint [<agent>] # validate configuration
tail -f .milieu/var/audit/audit.jsonl # watch the audit trailWhere to go next
- USER-GUIDE — every feature (agents, skills, knowledge, memory, messaging, projects, multimodal) in depth, with examples.
- OPERATIONS — the gateway, the REST API, declarative
provisioning with
milieu apply, the audit log, and troubleshooting. - DEPLOYMENT — take this beyond a laptop: systemd, cloud VMs, and Kubernetes.
- SECURITY — the trust model and a production hardening checklist.
- examples/ — four complete, runnable workspaces.