Chapter 7 – Intent as Code (Mission Objects)
Start with the smallest useful Mission Object:
mission_id: update-readme-api-endpoint
mission_version: 1
goal: "Document /v2/users in README.md"
scope:
modify:
- README.md
read_only:
- docs/api/openapi.json
quality_gate:
cmd: ./scripts/validate_readme.sh
fallbacks:
max_iterations: 3
on_fail: revertThat is already enough to do real work. It names the goal, the writable surface, the authority source, the gate, and the failure posture. It is not a renamed prompt. It is a contract the loop can parse, log, and rerun.
Fully autonomous engineering systems usually fail for a boring reason: intent is scattered across tickets and conversations, then compressed into a brittle instruction string. A raw instruction string is not a contract. It has no explicit scope, no verifiable gates, and no recovery path when things go sideways.
This chapter introduces the Mission Object: the typed, executable contract for a unit of work. Terminology note: a mission is the abstract objective (“update the docs”). A Mission Object is the versioned YAML/JSON artifact that encodes that objective plus scope, budgets, gates, fallbacks, and the instruction bundle the runner will compile into one bounded model request.
Chapter 0 called intent the attractor. A Mission Object is how you compile that attractor into something the loop can check. We will start small, then grow this into a production-shaped Mission later in the chapter and in Appendix C.
From Ad-hoc Instructions to Typed Mission Objects
Mission Objects (Intent As Code)
A Mission Object is the versioned contract for a unit of work: goal, scope, constraints, budgets, and gates. It turns a vague request into something the loop can compile, run, and audit.
Imagine you want an AI to update a specific section of your
README.md to reflect a new API endpoint. A typical request
might look like this: “Update the ‘API Endpoints’ section in
README.md to include /v2/users and describe
its pagination.”
What is missing is not model intelligence. It is contract structure:
Scope: Does it only touch that section, or can it add new sections?
Constraints: What format should the description take? Are there keywords it must include?
Quality: How do we know it did a good job? Is it grammatical? Is the new endpoint actually present?
Recovery: What if it messes up the existing formatting? How do we fix it?
A Mission Object transforms that opaque instruction into a clear, executable specification. It is a structured data artifact, usually YAML or JSON, that codifies the goal, scope, constraints, quality gate, fallbacks, telemetry, and the versioned instruction payload or prompt-template reference the runner needs. YAML is readable, but error-prone, so treat the Mission Object itself as a build input and validate it deterministically before you run anything.
Here is a fuller production-shaped Mission for the same README update task:
mission_id: update-readme-api-endpoint
mission_version: 1
goal: "Ensure README documents /v2/users"
dependencies:
- docs/api/openapi.json
scope:
modify:
- README.md
read_only:
- docs/api/openapi.json
do_not_touch:
- .github/**
- policies/**
edit_regions:
README.md:
- "## API Endpoints"
constraints:
forbidden:
- "Introduce endpoints not present in openapi.json"
acceptance_criteria:
must_contain:
- "/v2/users"
- "pagination"
must_not_contain:
- "/v1/users"
budgets:
max_files_changed: 1
max_lines_changed: 60
quality_gate:
cmd: ./scripts/validate_readme.sh
rollback_on:
- "quality_gate_fail"
- "scope_violation"
fallbacks:
max_iterations: 3
on_fail: revertThis is still readable. But now the system has something it can compile: bounded scope, explicit constraints, an executable gate, and a safe failure policy.
Mission Object anatomy (what vs. boundaries vs. verification)
When Mission Objects get muddy, loops thrash. The fix is to keep the categories crisp:
- Objective (the what): the intended outcome.
- Example: “Implement token validation.”
- Interface contracts (the surface area): the
skeleton you must preserve or evolve.
- Example: function signature, JSON Schema, API route table.
- In practice, these often belong in
scope.read_onlyand in your slice as deterministically extracted skeleton.
- Constraints (the how-not): forbidden behaviors and
hard boundaries.
- Example: “no network calls,” “do not modify
.github/**,” “only edit this region,” “do not add new endpoints.”
- Example: “no network calls,” “do not modify
- Acceptance criteria (the how to verify): statements
you can test deterministically.
- Good: “p99 latency < 100ms under benchmark X.”
- Bad: “fast enough.”
- Good: “output is valid JSON and conforms to schema Y.”
- Bad: “well structured.”
If a criterion cannot be validated deterministically, it doesn’t become Physics. It becomes an operator check: a required human review item, or a deferred quality goal you ratchet in later.
Mission Objects from natural language (assistive, not authoritative)
It’s tempting to use a model to convert a ticket into a Mission Object. That can be a useful accelerator, but treat it as drafting, not authority:
- A human validates that the structured Mission Object actually matches intent (especially for scope boundaries and forbidden actions).
- The Mission Object is schema-validated before it becomes a runnable input.
- Guardrails that protect the loop itself (protected paths, denylist, policy gates) must not be solely authored by the Effector they constrain (Chapter 10).
The companion repo (github.com/kjwise/aoi_code) includes
runnable mission templates such as
missions/templates/doc_sync.mission.json and
missions/templates/update_readme_api_endpoint.mission.yaml.
They use the companion repo’s local schema, but the semantics match the
canon here: goal + bounded scope + budgets + gates.
Mission Objects are stateful artifacts (Executable PR + state of record)
A Mission Object is not only an input to a run. It is the state of record for that unit of work: the thing you can point at in review, rerun tomorrow, and audit later.
It is also the artifact that actually moves through the recursive loop. The same Mission can survive multiple attempts with tighter findings, stricter budgets, or a different escalation outcome, while still preserving the original declared intent.
In practice, treat the Mission as an artifact with a run record:
- status: planned / running / completed / failed / reverted
- attempt count and last run id
- timestamps
- pointers to evidence in your Ledger (diffs, validator output, traces)
- prompt template / rendered request provenance when a model step is involved
The Mission stays human-readable, but it stops being prose. It becomes an executable work order: goal + scope + budgets + gates, plus the reusable instruction bundle, outcome, and evidence.
That state can accumulate as the Mission moves through loops within the loop. A retry can append findings. A slicer can append a context-packet identifier. A policy gate can append a decision trace. A deeper loop can append a child-run reference. What it should not do is silently rewrite the constitutional core of the Mission while it is active.
Lifecycle and versioning (Draft → Active → Completed)
Mission Objects live in version control, so they need a lifecycle that keeps provenance clean.
A pragmatic lifecycle:
- Draft: created from a human idea, a ticket, or a template.
- Under review: boundaries and gates are inspected like code (scope, budgets, rollback).
- Active: the runnable Mission Object version that a loop is executing.
- Completed / Reverted: finished with evidence
(
PASS+ diff) or safely aborted (FAIL+ trace). - Archived: retained for audit and pattern mining.
Two rules keep the Ledger honest:
- Once Active, Mission Objects are append-only. If
you need to change intent, create a new version
(
mission_version: 2) or a newmission_id. Don’t silently rewrite the record of what the loop was asked to do. - Every diff is attributable to a Mission Object
version. In practice, tag PRs/branches/commit trailers with
mission_id@mission_version, and store traces under a directory named the same way. That’s how you answer “which Mission produced this change?” without guesswork.
The easiest way to keep this straight is to split the Mission mentally into two regions:
- Constitutional core:
goal,scope,constraints,budgets,quality_gate,fallbacks - Append-only decorations: findings, attempt counters, context-packet ids, rendered prompt provenance, policy outcomes, evidence links, escalation decisions, child-loop references
If the work itself changes, version the Mission. If the loop learns more while executing the same work, decorate the active Mission and keep going.
The Meta-Patterns Applied to Missions
Mission Objects are where the Meta-Patterns become concrete.
1) Don’t Chat, Compile (Mission Objects are build inputs)
Chat is fine for ideation. For repeatable work, turn intent into an artifact you can version and rerun.
Store the Mission Object as data (
missions/update_readme.mission.yaml) and treat it like source code.Run a deterministic runner that produces a diff plus a Decision Trace (validator output, structured errors, and final
PASS/FAIL).
The point is provenance: you can rerun the same Mission Object tomorrow and debug the process, not just the outcome.
2) Physics is Law (the Mission must be gateable)
A Mission Object is only useful if the system can reject it deterministically. That means the Mission must define:
A bounded scope.
Executable gates (constraints, a
quality_gate, and/or tests).A revert policy when the gate fails.
If the gate fails, the correct result is FAIL and a
revert.
Schema-validate Mission Objects (input hygiene before autonomy)
A Mission Object is authority. A malformed Mission is not “bad luck” — it’s a bug in your intent surface.
Before you call any model:
- Parse the Mission deterministically.
- Validate it against a schema (and fail fast on missing fields or unknown keys).
- Enforce minimum invariants: bounded scope, non-empty gates, and explicit rollback policy.
Illustrative JSON Schema (trimmed):
{
"type": "object",
"required": [
"mission_id",
"mission_version",
"goal",
"scope",
"quality_gate",
"fallbacks"
],
"properties": {
"mission_id": { "type": "string", "minLength": 3 },
"mission_version": { "type": "integer", "minimum": 1 },
"goal": { "type": "string", "minLength": 10 },
"dependencies": { "type": "array", "items": { "type": "string" } },
"scope": {
"type": "object",
"required": ["modify", "do_not_touch"],
"properties": {
"modify": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
"read_only": { "type": "array", "items": { "type": "string" } },
"do_not_touch": { "type": "array", "items": { "type": "string" }, "minItems": 1 }
}
},
"constraints": { "type": "object" },
"acceptance_criteria": { "type": "object" },
"budgets": { "type": "object" },
"quality_gate": {
"type": "object",
"required": ["cmd"],
"properties": { "cmd": { "type": "string", "minLength": 1 } }
},
"rollback_on": { "type": "array", "items": { "type": "string" } },
"fallbacks": {
"type": "object",
"required": ["max_iterations", "on_fail"],
"properties": {
"max_iterations": { "type": "integer", "minimum": 1 },
"on_fail": { "type": "string", "enum": ["revert", "escalate"] }
}
}
},
"additionalProperties": false
}Treat this schema like Physics: version it, run it in CI, and protect it behind immutable boundaries (Chapter 10).
Constraint → gate mapping (what can be enforced vs. what needs review)
Mission Objects don’t magically enforce themselves. A constraint is only real when you can map it to a deterministic gate.
Examples:
| Mission field | Example | Enforcement |
|---|---|---|
scope.do_not_touch |
.github/** |
diff gate + protected paths + userspace boundary |
scope.edit_regions |
README.md under ## API Endpoints |
edit-region validator |
budgets.max_lines_changed |
60 |
diff budget validator |
acceptance_criteria.must_contain |
/v2/users |
deterministic content check (grep-style) |
| latency / cost targets | p99 < 100ms |
benchmark gate + budgeted circuit breakers |
| subjective criteria | “maintainable,” “best practice” | explicit human review item (not Physics) |
If you add a new constraint type and you don’t have a gate for it yet, make that explicit: either fail fast (“unknown constraint”) or route to escalation. Silent “constraints” are just prose.
3) Recursion (Missions are system currency)
Once Missions exist as artifacts, maintenance loops can emit them too.
A Sensor emits a concrete entropy signal (drift, unused config, missing docs).
The loop writes a Mission Object that scopes the repair and defines gates.
The same runner executes it and records evidence in the Ledger.
Now “maintenance” is a pipeline, not a calendar reminder.
Judge Output as Structured Error Objects (Signal-Rich Failure)
Once a model has attempted a task defined by a Mission Object, how do
we evaluate its output? A simple “yes” or “no” is not enough. We need
signal-rich failure that tells us why
something went wrong, so the loop can iterate intelligently or a human
can intervene with evidence. This is where the quality_gate
field in the Mission Object comes into play.
A quality_gate is not just a pass/fail check. It is the
mechanism that turns a failed attempt into structured feedback. That
feedback should be machine-readable and ideally conform to a predefined
schema.
Consider our README.md update. The
quality_gate references a validator command (and optionally
a schema for the validator’s output). After the AI proposes changes,
your system runs the validator. The validator doesn’t just return an
exit code; it emits structured findings.
Here’s an example of what that validator script might output if it fails:
{
"status": "FAILED",
"failed_checks": [
{
"check": "endpoint_description",
"expected": "The /v2/users line mentions pagination",
"actual": "- `/v2/users`: Manages user resources."
}
]
}That JSON output is not just a report. It is the next iteration’s input.
When you feed the failing checks back into the next Refine step (Feedback Injection), the model can fix one constraint instead of re-interpreting the whole mission.
If you revert after a failure, keep the candidate diff and the validator output in a quarantine directory (Salvage Protocol). You want the system safe by default, and you want humans to recover useful fragments without re-running the model.
A concrete Salvage Protocol example: loot the useful fragment
Imagine attempt 2 produced a good helper, but the overall patch failed a validator (wrong edit region, wrong formatting, missing required string). The runner should revert the working tree, but keep the evidence:
.sdac/workflow-quarantine/
update-readme-api-endpoint-v1/
attempt-2.diff
attempt-2.findings.json
On the next attempt, you can either:
- include the helper in the deterministic slice (so the model reuses it), or
- copy the helper into the final patch manually, without re-running the model.
Salvage is not “keep broken code.” It’s “keep the best parts of a failed attempt with provenance.”
This is why structured findings matter: they turn FAIL
into a specific next move. Instead of “validation failed,” the loop gets
facts it can route and inject:
- what failed (
check/error_code) - where it failed (
file/path, ideally with line) - expected vs actual, plus one concrete fix to try next
That feedback unit is what makes Refine converge instead of wander.
The Adaptation State Machine (ASM)
Receiving structured feedback is powerful, but what do we do
with it? This brings us to the Adaptation State
Machine: the allowed moves after the Judge step. The
fallbacks section in a Mission Object sets boundaries for
these moves.
In a minimal implementation, the core moves are:
stateDiagram-v2
direction TB
[*] --> Write
Write --> Judge
Judge --> Commit: PASS
Judge --> Iterate: FAIL (signal-rich)
Judge --> UpdateEnvironment: FAIL (missing constraints/tools)
Judge --> Revert: FAIL (unsafe or thrashing)
Iterate --> Write
UpdateEnvironment --> Write
Commit --> [*]
Revert --> [*]
Iterate: try again with tighter constraints and the exact failing evidence. This is only worth doing when the Judge output is signal-rich (file, line, failing check, expected vs actual).
Update Environment: change the deterministic context or tools, not the output. Add a missing schema to the slice. Add a validator. Add a fixture. If the system is failing because it lacks the contract, no amount of mutation fixes it.
Revert: abort and leave the Terrain unchanged. This is a successful outcome when the loop is thrashing or the blast radius is too high.
If the Judge returns PASS, you can commit or merge. If
it returns FAIL, the ASM decides whether it’s worth
spending more compute or whether you should stop and ask for a better
Mission Object.
Templates by Change Type
Not every engineering task is unique. Many fall into predictable patterns: synchronizing documentation, evolving schemas, or performing bounded refactors. Attempting to craft a unique Mission Object for each instance of these common tasks is inefficient and error-prone. This is where Mission Object Templates come in.
Templates pre-package the common goal,
scope, constraints, quality_gate,
and fallbacks for specific categories of changes. They
allow engineers to invoke complex, AI-driven workflows with minimal
input, inheriting a robust set of guardrails and validation logic.
For copy-and-specialize artifacts, keep one source of truth: Appendix C holds the canonical minimal and production Mission templates, alongside the paired output-contract patterns. This chapter names the template families and design rules; Appendix C holds the reusable text.
Here are examples of how templates can simplify recurring Software Development as Code (SDaC) workflows:
Driver Pattern (decouple intent from mechanism)
Mission Objects and templates should express what to do, not which
tool to use. “Run tests” is intent; pytest vs
vitest vs mvn test is mechanism.
One simple pattern is to name the intent and let a driver map it to the repository substrate. The Mission can say “run tests for this target,” and the driver resolves the actual command from deterministic identity (language, build system, repo conventions).
Your driver selects the command based on the repo (language, package manager, existing CI), ideally using Context Graph identity for the target node. That keeps Mission Objects portable across stacks and keeps tool details out of intent.
A common implementation: the Makefile is the driver interface
If you standardize on a canonical CLI per repo (or per language-root), you can implement the Driver Pattern by convention instead of by introspection.
This is the same move you saw in Chapter 1 (“Makefiles as the repo’s
command panel”): make becomes the stable control surface,
and each repository maps that surface to its local toolchain.
- Mission Objects express intent (
run_tests). - The runner calls one mechanism everywhere (
make testin the target root). - The repo’s
Makefilehides the incantation (pytestvsnpm testvsmvn test) and bakes in paths, env vars, and flags.
This works especially well when your boundaries are clean: one language/toolchain per repo, or a monorepo that is deliberately split into language-root folders with their own command panels. In that world, “select the mechanism” often reduces to “cd into the right root and press the same button.”
Make is only one substrate. You can swap it for any task runner
(just, task, bazel,
npm scripts, CI-native wrappers). The Driver Pattern is the
invariant: one canonical interface per project, with swappable
implementations underneath.
Driver resolution algorithm (identity → command, fail fast)
Keep driver resolution deterministic and explicit:
- Read node identity from the Context Graph (language + build system).
- Look up a driver in a registry (built-in defaults plus repo overrides).
- If multiple drivers match, apply precedence (most specific match wins).
- If no driver matches, fail fast with an actionable error (“no test driver for rust+cargo”).
The key is that the system chooses the mechanism from deterministic identity. The model never guesses which toolchain to run.
Worked polyglot
example: pytest vs. jest
Assume a monorepo with two packages:
services/apiis Python (tests run viapytest)services/webis JavaScript (tests run viajest)
The Mission Object expresses intent only: “run tests for
services/api” and “run tests for
services/web.”
The driver uses deterministic identity (computed from manifests like
pyproject.toml / package.json) to choose the
mechanism. If a package has no manifest, the driver fails fast. If a
package has no test command, the driver fails fast. The model never
invents toolchains; the substrate tells you what exists.
Documentation Synchronization
Problem:
README.mdor API documentation often drifts out of sync with the actual code or OpenAPI specification.Template: A
DocSyncMissiontypically takes a source of truth (OpenAPI/schema) and a target doc surface, and constrains edits to named sections. The quality gate checks that required fields/paths appear and formatting stays canonical.
Schema Evolution
Problem: Adding a field to a database schema requires corresponding updates in ORM models, API DTOs, and potentially UI forms.
Template: A
SchemaEvolutionMissiontakes a schema change (add field / rename / deprecate) and expands scope deterministically to the dependent surfaces (models, DTOs, migrations). The quality gate compiles, types, and tests the affected roots.
Bounded Refactor
Problem: Applying a consistent rename across a codebase or extracting a small helper function.
Template: A
BoundedRefactorMissiontakes a small, mechanizable change (rename, extraction, “make this function pure”) and applies strict diff budgets. The quality gate runs the local Immune System plus static checks.
Templates let teams establish pre-vetted patterns for recurring changes. That improves consistency, reduces novel instruction-injection failures, and lets engineers spend attention on the change itself rather than rebuilding guardrails for every run.
The companion repo includes a driver-demo target that
runs resolve_driver.py to demonstrate how a driver can be
resolved from a simple registry based on file type.
Actionable: What you can do this week
Define a Simple Mission Object: Pick a straightforward, low-stakes task in your codebase (e.g., update a comment, add a line to a specific section of a non-critical file). Start from Appendix C’s minimal Mission template, then specialize the
goal,scope, and any simpleconstraints.Mock an AI Executor: Write a small script (Python, Bash, Node) that reads your Mission Object YAML. For now, have it simply print the
goalandscope. Then, manually make the requested change in the target file.Implement a Basic Quality Gate: Add a
quality_gateto your Mission Object. Create a separate script (e.g.,validate_task.sh) that takes the modified file as input and performs one or two simple checks (e.g.,grepfor a specific string, check file size). Have this script output a simple JSON object indicatingstatus(“PASSED” or “FAILED”) and areason.Wire up a “Judge”: Modify your executor script from step 2 to call your
quality_gatescript. Based on the JSON output, print “Mission PASSED” or “Mission FAILED” along with the reason. You now have the core components of an auditable SDaC loop.