Skip to main content
You author a mission as a directory: a manifest, whatever the agent has to work against, and the rules that grade it. This walkthrough builds one end to end, so the bundle is complete and correct for the day ingestion ships. Time: about 30 minutes · You need: Quickstart and a registered agent · Interface: CLI
Ingesting your own mission bundle is coming soon. Everything below — the manifest format, artifacts, checks, rubric, terrain — is the spec you author against, but neither surface that would install one is available yet: xorcise mission ingest prints a coming-soon notice, and the Ingest a bundle button on the Missions page opens a preview of the feature rather than a directory picker.To get missions today, pull them from the free XORCISE library: xorcise mission list to browse, xorcise mission pull <id> to install, or the same thing from the Missions page. No account, no key, no sign-in.

Lab or static

Decide this first. metadata.type is required, it takes exactly two values, and it changes what you author, how the run works, and what the agent is told. Pick lab when the point is reaching and exploiting something. Pick static when the point is analysis — a capture, a binary, a disk image, a log set. This guide builds the lab first and shows the static difference at each step.

1. Create the bundle directory

Name the directory after the mission id you intend to use. Nothing enforces that locally, but it keeps the two in step, and the XORCISE library requires the id to match the display name.
For a static mission, make a directory for the payloads instead:
You should see an empty tree ready for the manifest:
Output

2. Build what the agent works against

For a lab, write a compose file. Give every network an explicit name — the agent reaches the mission through the networks you list as entry networks, so an unnamed network is unreachable.
docker-compose.yml
services/web/Dockerfile
For a static mission, there is no compose file and no image. Put the payload in the bundle instead:
Confirm the lab environment builds on its own before XORCISE tries:
You should see the parsed compose file printed back, with player under networks. An error here is a compose error, and ingestion will report it later and less clearly.

3. Write the manifest

Create mission.json at the bundle root. Start with identity and the mission — the two blocks that are always required.
mission.json
Three things in that block do real work:
  • objective is the agent’s whole brief. summary is for you and the catalog; the agent never sees it.
  • <web-target-ip-> is a placeholder XORCISE substitutes with the resolved address of the web service. Compose service names do not resolve on the per-run network, so writing http://web:80 gives the agent an address it cannot reach.
  • static_ips is what creates targets at all. It pins web on the player network, which is an entry network, so web becomes a target and the placeholder resolves. A service pinned only on a non-entry network gets no address — that is how you build a pivot the agent has to earn.
A static manifest replaces the whole environment block with attachments, and its objective stands alone because there is nothing to address:
mission.json
You should see valid JSON when you parse the file:
Output

4. Declare what the agent submits

Add an artifacts array. The flag is the artifact named flag — there is no separate flag mechanism.
mission.json
Two traps here, both quiet:
  • required: true does not gate anything. It prints (required) in the prompt and stops there. A missing artifact costs the score of the checks that read it, and nothing else. Declare an artifact and write no check against it, and it cannot affect the score at all.
  • The lookup key is the name the agent submits, not the name you declared. They line up only because the prompt told the agent what to call it. Name your artifacts something an agent will copy exactly, and repeat the name in the objective — the mission prompt is the only place the agent is ever told.
You should see both names come back out of the manifest:
Output

5. Define the grading

Every run is scored as 0.5 × deterministic + 0.5 × judge, and the split is fixed. checks earn the first half; rubric earns the second. Ship both — a mission with only checks, or only a rubric, caps at 50%.
mission.json
A check reads a value from a source using ref as the lookup key, then asserts over it with an op. The full source, ref and op vocabulary is in Checks and ops; the rule to learn now is the weighting. Check weights are all-or-none. Either every check declares one and they sum to 1.0, or none does and XORCISE splits the half equally. A mix fails at ingest with a clear message.
Rubric weights are not all-or-none, and there is no equal split. If no criterion declares a weight, the judge half scores exactly 0.0 and reports judge_status: "ok" — a full 50% disappears with no error anywhere. Weight every criterion.
Write rubric criteria as things a reader of the transcript could confirm. The judge is a model reading your text against the run record, so “identified the injectable parameter” is gradable and “solved the mission well” is not. You should see both weight sets total exactly one:
Output

6. Ingest the bundle (coming soon)

Installing a bundle from disk is not available in this release. Every form of the command — with a path, without one, pointing anywhere — prints a notice instead:
Output
The Ingest a bundle button on the Missions page behaves the same way: it opens a preview of the feature, not a directory picker. Neither surface installs anything, so keep the bundle on disk and check it against the rules in Fix a rejected bundle below. When it ships, ingestion will be the only validator — there is no mission validate command and no scaffold command — so it is where every mistake surfaces. It validates the manifest, builds every service in the compose file, and installs the result as xorcise-fused/sqli-login:latest. A static mission skips the build entirely and installs in seconds.

7. Run it and read the score

Running a mission and reading its score works today — against a mission you pulled from the library (xorcise mission pull <id>). It is the same loop your own mission will use once ingestion ships, so walk it now with a pulled mission id in place of sqli-login. Create a run against the mission with a registered agent, launch the agent, and read the result.
The command prints a run id. Use it in place of <run_id> below.
That prints the command that launches your agent already pointed at this run. Run it, let the agent work, and read the result once the run ends:
You should see the overall score split into its two halves, every check listed with the effective weight XORCISE gave it, and every rubric criterion listed as a judge criterion in the author’s own wording. Once your own mission is ingestable, that report is where flag-correct, found-injection and read-flag show up — the loop closed, your rubric scoring a real agent. If the judge half reads 0.0 with judge_status: "model-not-configured", the mission is fine and no judge model is set. Configure one and re-grade the sealed run rather than running it again.

Fix a rejected bundle

Ingestion reports the first rule a bundle breaks. These are the ones that catch new authors — write against them now and the bundle is ready when the feature ships.
type is required and is either lab or static. The v1 values ctf, scenario and boot2root no longer exist.
Every block rejects unknown keys. Usually a v1 name — difficulty is now proficiency, competencies is now skills — or a typo.
Add environment, or change type to static.
A static mission with nothing to analyse is not a mission. Add the file and declare it.
The path is relative to the bundle root, and it defaults to docker-compose.yml.
Same — attachments[].path is relative to the bundle root, not to the manifest’s own directory.
Remove every check weight, or give every check one.
Declared check weights must total exactly one.
Each op takes an exact set of args. See Checks and ops.
Rename your mission_id. An id belongs to one source, and a local ingest would shadow the library’s.
Two failures produce no message at all. A wrong terrain key is never validated — the bundle passes and the map is silently wrong. And an unweighted rubric grades to 0.0 while reporting success.

Iterate on a mission

When ingestion ships, you edit the bundle and ingest it again. The same mission_id from the same source is a version bump with an atomic swap, so a failed re-ingest leaves the working install untouched. Grading reads the installed manifest, never your bundle directory. Editing a check or a rubric criterion changes nothing until the bundle is ingested again. Existing runs keep the rules they were graded under.

What you just did

  • Chose an execution class, which decided everything else — see Missions.
  • Wrote an environment and pinned a target, which is what gives an agent somewhere to go.
  • Declared artifacts, which is the only channel the agent has for reporting findings — see Connect any agent.
  • Wrote one check and two rubric criteria, which are the two halves of every score — see Checks and ops.
  • Produced a complete bundle, ready for the day ingestion ships. Until then, install missions from the free library with xorcise mission pull <id>.

Next steps

How grading works

The arithmetic behind the number your mission just produced.