mission.json at its root. That file is the complete specification of a mission: it declares the environment to build, what the agent submits, and how the run is graded.
Authoring and ingesting your own bundle is coming soon.
xorcise mission ingest is not enabled in this release — every form of the command prints a coming-soon notice, and the Ingest a bundle button on the Missions page opens a product preview rather than installing anything. This page remains the reference for the format, so you can prepare bundles now and be ready when ingestion ships.To get missions today, browse the free XORCISE library with xorcise mission list, install one with xorcise mission pull <id>, or use the Missions page in the web UI. No account, no key, no sign-in.Every block in the manifest rejects unknown keys. One misspelled or leftover field anywhere in the file fails validation with
invalid mission.json, so check spelling before you check logic.Bundle layout
Ingestion copies the whole directory into the install and into the built image, including
.git and __pycache__. Keep the bundle lean.
Every top-level field
metadata
string
required
Stable id. Becomes the install directory name and the id you pass to every command.
string
required
Display name.
string
required
The agent-facing mission, rendered into the mission prompt.
"lab" or "static"
required
The execution class. Drives every runtime branch.
string
default:"\"\""
Catalog blurb shown to you, never to the agent.
string
Skill level on the XORCISE ladder, in ascending order:
Novice, Advance Beginner, Competent, Proficient, Expert. The web UI fills one of five pips for a ladder value, and maps the older scale onto the nearest tier — easy and beginner to the first, intermediate and medium to the second, hard and advanced to the fourth. A value it recognises from neither set draws no pips and renders as a plain muted label.string
Primary domain, one of
Intelligence, Detection, Investigation, Engineering, Penetration, Protection. These are the values the catalog’s Specialty facet groups by, and each one carries its own canonical skills list.array of strings
default:"[]"
Techniques the mission exercises. A browse facet.
array of strings
default:"[]"
Technology present in the environment.
type classifies how the mission runs, not what genre it belongs to. A lab deploys containers on a per-run network; a static mission deploys nothing and hands the agent files. The v1 genre values ctf, scenario and boot2root no longer exist, and a manifest carrying one fails with invalid mission.json: metadata.type.
Two other v1 names were renamed and now fail as unknown keys: difficulty is proficiency, and competencies is skills.
Target IP placeholders in the objective
Compose service names do not resolve over the per-run network. The agent gets routed IP addresses and nothing else, so a service name written into the objective points at nothing. Write the placeholder<service-target-ip-> instead, where service is a key of environment.static_ips. XORCISE substitutes the resolved address when it renders the prompt:
static_ips is the only source of both the agent’s target list and this substitution. A static mission has no targets, so its objective must be self-contained.
environment
Required forlab, omitted for static. A static mission that declares one still validates, but the block is ignored and logged as dead weight.
string
default:"\"docker-compose.yml\""
Path relative to the bundle root. Must exist on disk.
array of strings
default:"[]"
Compose network names the agent can reach. Empty falls back to
default.object
default:"{}"
service → network → last octet. Pins an address and publishes it as a target.- A service pinned only on a network that is not an entry network gets no address and is not a target. That is how you build a pivot.
- The first carved match wins, so a service gets exactly one address.
- Omit
static_ipsentirely and the agent gets no targets at all. This is the most common reason an agent cannot reach anything.
artifacts
string
required
The name the agent submits under.
string
What it is. Rendered into the prompt.
boolean
default:"true"
Advisory only.
flag. There is no separate flag field, flag endpoint or answer hash.
required is a label in the prompt, nothing more. Grading never reads this array — a missing artifact resolves to nothing and every check against it fails. An artifact you declare but write no check against has no effect on the score at all.
rubric
string
required
Stable id, reported per criterion on the result.
string
required
What the judge scores against, in plain language.
number
This criterion’s share of the judge half.
checks
string
required
Stable id, reported per check on the result.
string
required
One of
artifacts, otel-stats, observed-facts.string
required
The lookup key into that source, never the expected value.
string
required
One of
equals, matches_format, observed, lesser_than.object
default:"{}"
The comparison value. The key set is exact per op.
number
This check’s share of the deterministic half.
0 < weight ≤ 1.ref names are valid inside it, and the exact args each op requires are in Checks and ops. A missing or extra args key fails at ingest.
Check weights follow one rule, enforced at ingest: every check declares a weight and they sum to 1.0, or none does and XORCISE splits the half equally. A mix fails with checks must ALL declare weight or NONE declare it (no mix).
intel
string
required
Stable id.
string
required
The intel item, in the order you declare it.
attachments
At least one is required for astatic mission. Optional for a lab.
string
required
The key the agent fetches by. Need not match the filename.
string
required
Path relative to the bundle root. Must exist on disk.
string
MIME type.
string
Declarative only — nothing verifies it.
string
What the file is.
name and receives a short-lived download link.
terrain
The authored attack path, rendered as a live map on the run page. It is display-only: authoring terrain never changes a score. Omit it and XORCISE draws a fallback map fromenvironment.static_ips — one group per network, one node per service, no edges. A static mission with no terrain gets no map at all.
summary is a string, and it is prompt input rather than decoration — the model that animates the map reads it as the mission summary. Write it properly.
groups
string
required
Referenced by every node’s
parent.string
Display name. Defaults to the
id.string
Shown on the map and given to the model.
string
Plain English: when has the agent found this segment?
Fog-of-war. The group appears once discovered.
nodes
string
required
Unique. A
: inside it is convention, not syntax.string
required
An existing group
id.string
default:"\"service\""
Free-text node kind, used for the icon.
string
Display name. Defaults to the
id.string
Shown on the map and given to the model.
string
Plain English: when has the agent found this node?
string
Plain English: when has the agent finished with it?
boolean
default:"false"
Marks the run’s goal. Declare exactly one.
edges
string
required
Unique.
string
required
A known node or group id, or the literal
agent.string
required
Same as
src.string
How the hop works. The model reads this to decide when the edge lights up.
What terrain silently discards
Nothing below is caught at ingest. The bundle passes validation cleanly and the map is wrong.- The group key on a node is
parent, notgroup. A node with noparent, or one naming a group that does not exist, is dropped. - A
terrainblock containing onlyedgesis ignored entirely and thestatic_ipsfallback runs instead. Authored edges need at least one authored group or node to survive. - Thirteen ids are reserved for the infrastructure scaffold and are dropped if you author them:
agent,xorcise,hs,rc,collector,hs:register,hs:join,hs:derp,rc:prompt,rc:attachments,rc:artifacts,rc:intel,rc:done. Edges pointing at a dropped id die with it.agentremains legal as an edge endpoint — that is how you draw the agent’s entry point. objective: trueis first-wins. Later flagged nodes keep the flag but are not the objective.kind,order,state,active,discovered,roleandgt_prevare not authorable and are ignored.
:8080 through the pivot”), never as internal state.
A minimal lab manifest
Every key here is either required or load-bearing. This bundle satisfies every rule in Validation as written.mission.json
docker-compose.yml
services/web/Dockerfile
entry_networksnamesplayer, which exists in the compose file. Naming a network that does not exist gives the agent nowhere to go.static_ipspinswebonplayer, sowebbecomes the run’s one target and<web-target-ip->resolves.- The single check declares
weight: 1.0, satisfying the sum-to-one rule on its own. Dropping the weight would also be legal, because then no check declares one. - Both rubric criteria carry weights. Omitting both would score the judge half
0.0. summary,intel,attachmentsandterrainare all absent, and all optional.
A minimal static manifest
The same subject shipped as a static mission: no image, no compose file, no network, no targets. The agent works entirely from the attachment.mission.json
- No
environmentblock, no compose file, no Dockerfile. Nothing is built and nothing is deployed. - One attachment is the minimum, and the whole contract. Without it the manifest fails with
static mission requires at least one attachment. nameis what the agent fetches by;pathis where the bytes live. They do not have to match.- The objective carries no
<service-target-ip->placeholder, because a static run has no targets and no network to resolve one on. - The check declares no weight, which is legal because no other check declares one either.
Validation
Every rule below is enforced in one place — the ingestion path that reads the bundle — and nothing is written until all of them pass. There is no separatemission validate command and no scaffold command, and ingestion is not something you can run yourself in this release, so until it ships this table doubles as the checklist to read a manifest against by hand.
Terrain is not validated. A typo inside a terrain node passes validation and produces a wrong map.
One exit code to know for when ingestion ships:
ingest exits 3 while a build is still running server-side. Exit 3 is not a failure, and a script that treats every non-zero code as one will misread a healthy build.
Related pages
Checks and ops
What each source resolves and what each op asserts.
Author a mission
The walkthrough from empty directory to a graded run.