Skip to main content
A mission bundle is a directory with a 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

Ship both checks and rubric. XORCISE scores every run as 0.5 × deterministic + 0.5 × judge, and those two halves are fixed, so a mission with no checks — or no rubric — can never exceed 50%.

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 for lab, 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.
XORCISE splits the run’s CIDR across the entry networks in declaration order — one network takes the whole range, and several take the smallest equal power-of-two split. It then resolves each pinned service to the network address plus its octet. Three consequences worth memorising:
  • 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_ips entirely 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.
The flag is an artifact named 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.
The judge divides by the summed weights of the criteria it could actually grade, so criteria it returns as unknown are dropped rather than scored zero.
Rubric weights have no equal-split fallback. If no criterion declares a weight, the judge divides by zero known weight and the judge half scores exactly 0.0 — reported as judge_status: "ok", with no error anywhere. Weight every criterion or expect half your score to vanish silently.

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.
What each source resolves, which 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.
Intel is offered per run. The mission prompt advertises the intel endpoint only when that run’s intel allowance is above zero, so declaring intel does not force it on anyone.

attachments

At least one is required for a static 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.
Bytes are never inlined in the manifest. At run time the agent asks for an attachment by 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 from environment.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?
boolean
default:"false"
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, not group. A node with no parent, or one naming a group that does not exist, is dropped.
  • A terrain block containing only edges is ignored entirely and the static_ips fallback 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. agent remains legal as an edge endpoint — that is how you draw the agent’s entry point.
  • objective: true is first-wins. Later flagged nodes keep the flag but are not the objective.
  • kind, order, state, active, discovered, role and gt_prev are not authorable and are ignored.
Conditions are natural-language prompts, not an expression language. A model reads them against the agent’s spans and decides when a node lights up, so write them as observable behaviour (“the agent reaches the internal service on :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
Reading the manifest against the compose file:
  • entry_networks names player, which exists in the compose file. Naming a network that does not exist gives the agent nowhere to go.
  • static_ips pins web on player, so web becomes 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, attachments and terrain are 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
What changes against the lab above:
  • No environment block, 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.
  • name is what the agent fetches by; path is 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 separate mission 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.

Checks and ops

What each source resolves and what each op asserts.

Author a mission

The walkthrough from empty directory to a graded run.