The builder is a form over the same team file you could write by hand: everything you fill in appears in the file pane beside it, live. The file is the deployment — copy it into your repo when you like the shape. Nothing deploys unjudged: Plan is the only path to Deploy.
name is the team's identity — lowercase, like a repo. Memory, when you add it, follows this name across redeploys, so renaming a team opens a fresh memory. description is read by strangers first: the console shows it on every screen about this team.
Each input row becomes a
required (or optional) argument of every run. One input named
topic means a run starts as
harmonia run dep-1 -i "topic=…" — and your agents
reference it as ${input.topic} in their
instructions.
An agent is four decisions: a name
, the field it writes, its
instructions, and — only when order
matters — what it runs after. Leave
model blank and the service decides
(receipts record requested vs served); set it per agent
(deepseek/deepseek-chat, anthropic/claude-sonnet-5)
to mix providers inside one team — or give a preference list,
model = ["a/x", "b/y"], and the first the service
resolves is the one that serves, the receipt naming both the ask
and the answer.
[agents.writer]
instructions = """
Draft a landing page from: ${input.topic}"""
writes = ["draft"]
Fields your agents write or read become the team's
shared state automatically — the builder keeps the
[state] table for you.
Plan sends the file to the service's judge. Refusals come back in the judge's own words (an unknown field, a missing model, a cycle in the ordering); a clean plan shows the execution picture — who starts together, who lands where, which model would serve each agent. Deploy appears only after a clean plan, and editing the form withdraws it until you plan again.
The success card hands you the run line. The console shows what the run was asked, what it produced, where the time went, and the sealed receipt chain — including how the concurrency actually played out.
Concurrency is the default. Every agent whose after is blank starts the moment the run does. You define the shape of the work with two controls — after (ordering) and landing (how same-field writes meet) — and there are only four shapes to know:
Give several agents the SAME writes field with landing = extend. They run at once; every contribution composes; collisions retry and land. The console's contention screen counts applied vs retried — contention is a fact on the record, not a failure.
Give each agent its OWN field. Fully parallel, no contention: each writer holds its field exclusively and the plan says so.
after = ["a", "b"] makes an agent wait for those agents to settle, then read what they wrote via reads. Fan out concurrent writers, fan in one folder.
Same writes field, every writer landing = vote. All run at once; the majority of cast ballots lands; dissent is receipted, not erased — the tally rides the run's record as vote:field rows, abstentions named.
The quickstart's briefing team is all three at once — two researchers extending one notes field (panel), an editor after both (pipeline), each also free to hold its own field (map):
[agents.facts] [agents.risks] writes = ["notes"] writes = ["notes"] landing = "extend" landing = "extend" [agents.editor] reads = ["notes"] writes = ["briefing"] after = ["facts", "risks"]
seats = 5 lets five agents call that key at
the same time; a sixth queues and its receipt says how long it
waited (lane_wait). Ask your operator for the
roster's lanes, or mix models per agent to spread across
providers.An agent can be a command instead of a model: work names the words, and the box it runs in is law. At the local residence the body runs confined — no network, a private tmp, the typed state exchange as the only door in or out. Declare residence = "sandbox" and the same body runs in a disposable machine instead — or residence = "vm" for the family's vm carrier; the receipt names the confinement that actually served either way. Work bodies carry no model or effort, and instructions belong to model agents.
[agents.checker] work = ["python", "check.py"] residence = "sandbox" writes = ["report"]
[hooks.sentry] gives a
deployment webhook entry points: harmonia hooks
DEPLOYMENT prints each hook's URL (the token in it IS the
caller's auth — treat the line as a credential). POST any JSON
and a run fires with hook provenance on the narrative; payload
fields land on declared inputs by name, the static
input seed underneath. Wrong name or wrong token
answer one opaque refusal.
[environment] declares
the box's world — the infra third of the file: base =
"node-20" (a carrier-face word; advisory at local),
repos = [...] cloned once into a prepared root every
body sees read-only, and [environment.setup] with
work = ["npm", "ci"] run once at deploy — a failing
setup refuses the deploy with its own words, never a mid-run
surprise.
grants widen the box, and
only the box: grants = { net = true, read = ["/data/docs"],
files = ["/scratch"] } — network, read-only paths,
read-write paths. Paths are absolute and must sit under roots the
service was constructed to bind; anything else refuses at plan,
with the roots named. What an agent could reach is never a
guess — the receipt carries the grants that served.
Name the same files root from TWO agents and it becomes a mediated workspace: each body works on its own copy and lands its writes per file at exit — a write is admitted only if its target hasn't moved since the body observed it; a stale write is refused with the diff on the receipt, the admitted files stand, and a rerun re-derives from what's actually there. N agents, one tree, nothing lost silently.
Add a memory field and
every run's settlement folds onto it — extend accumulates
(a log the team keeps), replace keeps the latest. An agent
reads it like any field (${state.knowledge}); a
writer that names it banks into it. The fold chain is sealed and
hash-linked, and the console's memory screen shows what the team
knows now and every fold that got it there. Memory follows the
team's name — redeploys reattach, renames start fresh.
Copy-paste teams in the repo's demos/recipes/, each exercising a schedule, memory, or a gate: daily-brief (fires itself daily, remembers what it already told you), repo-watcher (the sentinel you feed one line from cron — the feed stays yours until declared effects land), and gated-release (drafted by agents, shipped only when you approve — the ask shows the draft, your note steers the final text). Point the stack's --notify at any plain-text relay and the waiting ask reaches your pocket.
Refusals are the product working: an agent writing a field nobody declared, an ordering cycle, an effort the held model cannot carry, a memory shape that does not match what the name already holds. Each refusal names the fix. Nothing that plans clean is refused at deploy.