Intermediate representation (IR)¶
The IR is a pure-data model of a swarm. It lets GenSwarms describe, validate,
mutate, and drive a swarm as plain JSON-shaped data — with no Code.eval, no
shell, and no atom minting from untrusted input. It is also the foundation the
gsp package ecosystem (sharing reusable agent definitions) builds on.
It lives in Genswarms.IR.* and is exposed through the Genswarms.IR façade.
Status
The IR core, the config→IR translator, the op-validation policy, the
reconcile/actuation layer, and the default validation gate are all
wired and in use. The package registry (swarmidx) and ref resolve step
(constraints → content digests) are future work; until then, configs map to
inline/oci:/apple_container/ssh refs rather than published
swarmidx: packages.
The two representations¶
| Representation | Module | What it is |
|---|---|---|
swarm.state (IR1) |
Genswarms.IR.State |
A snapshot of a swarm: agents, objects, topology, options — in a declared phase (desired or observed). |
swarm.overlay (IR2) |
Genswarms.IR.Overlay |
An ordered log of mutation events (add_agent, scale_agent_group, bump_package, …) that folds over a swarm.state. |
A swarm is the result of folding overlays onto a seed state:
fold/2 (Genswarms.IR.Fold) is pure: it applies events in seq order and
performs no runtime effects. Executing the result against the live system is a
separate concern (see Actuation).
swarm.state (IR1)¶
Each agent has three orthogonal slots, each answering a different question:
| Slot | Question | Example |
|---|---|---|
body |
who is the agent / what does it do (its persona, today the skills) | {"ref": "inline:researcher", "kind": "data"} |
model |
which LLM | {"ref": "openrouter:anthropic/claude-sonnet-4", "attested": true} |
backend |
where it runs | {"ref": "bwrap"} / {"ref": "oci:web", "kind": "data"} / {"ref": "apple_container", "image": "szc-agent-code:latest"} / {"ref": "ssh", "host": "pi@h"} |
Objects have a handler slot (kind: code). References (Genswarms.IR.Ref) carry
a content digest when they are content-addressable (swarmidx:/oci:), or are
marked attested / represented as backend metadata when they are not
(openrouter:, ssh, apple_container).
A swarm.state must satisfy the structural invariants on parse: unique node
names, every topology endpoint exists, and slot-typing — body/policy are
kind: data, handler is kind: code. That data/code split is the privilege
boundary: data is loadable from anywhere, code is not.
swarm.overlay (IR2) — op catalogue¶
op |
Payload | Effect |
|---|---|---|
add_agent / add_object |
a full agent/object | add a node (name must be new) |
remove_agent / remove_object |
{name} |
remove the node and its edges |
add_topology_edges / remove_topology_edges |
{edges} |
add/remove edges |
scale_agent_group |
{base_name, target_count} |
materialize base#1..base#N |
bump_package |
{target, field, from, to} |
swap a slot's digest (from must match) |
set_options |
{options} |
merge into options |
update_config |
{target, config} |
merge into a node's config |
Unknown ops fail validation — they are never silently ignored, and op strings are matched against a fixed set (no atom minting).
The public API¶
alias Genswarms.IR
{:ok, state} = IR.state(state_map) # parse + validate a swarm.state
{:ok, overlay} = IR.overlay(overlay_map) # parse + validate a swarm.overlay
# apply one proposed op: security policy first, then structural fold
{:ok, state2} = IR.apply_op(state, event)
# apply a whole overlay, op by op
{:ok, state3} = IR.apply_overlay(state, overlay)
# fold a seed + overlay into the desired state
{:ok, desired} = IR.materialize(seed, overlay)
# checkpoint + log compaction
{:ok, checkpoint, remaining} = IR.compact(seed, overlay, at_seq)
apply_op/3 is the single choke point where both the security policy
(IR.OpPolicy) and the structural preconditions (IR.Fold) are enforced.
From your config¶
Genswarms.IR.FromConfig translates the existing .exs/.json/.yaml
swarm configuration into a validated swarm.state:
| Config | IR |
|---|---|
skills / presets |
body {ref: "inline:<name>"} + overrides |
model: "x/y" |
{ref: "openrouter:x/y", attested: true} |
backend: :bwrap / :local / :mock |
bare refs {ref: "bwrap"} … |
backend: {:docker, n} |
{ref: "oci:<n>", kind: data} |
backend: :apple_container |
{ref: "apple_container"} |
backend: {:apple_container, n} |
{ref: "apple_container", image: n} |
backend: {:apple_container, n, opts} |
{ref: "apple_container", image: n, opts: opts} |
backend: {:ssh, "u@h"} |
{ref: "ssh", host: "u@h"} |
object.handler Mod |
{ref: "module:<Mod>", kind: code} |
The Apple container ref is intentionally not content-addressable in the current
IR mapping: it stores the backend choice and optional image/options metadata, but
does not claim an OCI digest. Docker keeps the existing oci:<image> mapping.
Genswarms.IR.ToConfig round-trips these Apple forms back to
:apple_container, {:apple_container, image}, or
{:apple_container, image, opts}.
The default control-plane gate¶
The IR is wired into the orchestrator as a strict, fail-closed gate
(Genswarms.IR.Gate), so it governs every swarm without changing how agents are
spawned:
- On swarm start — the config must translate to a valid
swarm.state(the §6 invariants). An invalid or untranslatable config is refused before spawning. - On
add_agent— rejects host-escape backend config keys (subzeroclaw_path,extra_ro_binds,extra_rw_binds,extra_path) and the per-swarm agent cap. - On
scale_agent_group— enforces the agent cap.
The cap defaults to config :genswarms, :max_agents_per_swarm (100) and applies
to dynamic mutations, not to operator-authored configs.
Actuation¶
Genswarms.IR.Reconcile.plan(desired, observed) computes the ordered actions
that bring a live swarm to a desired state (start/restart/stop nodes, add/remove
edges) — pure, no runtime access.
Genswarms.IR.Executor runs that plan against the orchestrator: it reads the
live config (observed) and translates each action into a SwarmManager call.
Executor.reconcile(swarm, desired) does the whole loop — observed → plan →
apply.
Design spec¶
The normative format and semantics (the ref model, the §6 invariants, the op catalogue, fold/materialize/compaction) are defined in the GenSwarms IR specification. This page documents what is implemented and usable today.