Part III Scale It From Loop to System
17 min read

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: revert

That 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.

Why it matters
The tighter the mission boundary, the smaller the search space. That is how you get faster convergence without turning a prompt into pseudo-authority.
Constraint tightness 55%
mission.yaml

Constraint → Deterministic Gate
Derived interface
Expected retries x
Convergence rate /100
Drift risk /100
Budgets (bounded exits)
Max calls
Max latency
If the budget is exceeded, the loop exits with a deterministic failure you can reason about.
Mission State of Record
Status
Attempt
Last run
Trade-off
Flexibility %
Constraint strength %
Validation Pipeline
Schema compile
Scope gate
Quality gate

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:

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: revert

This 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:

  1. Objective (the what): the intended outcome.
    • Example: “Implement token validation.”
  2. 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_only and in your slice as deterministically extracted skeleton.
  3. 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.”
  4. 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:

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:

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:

  1. Draft: created from a human idea, a ticket, or a template.
  2. Under review: boundaries and gates are inspected like code (scope, budgets, rollback).
  3. Active: the runnable Mission Object version that a loop is executing.
  4. Completed / Reverted: finished with evidence (PASS + diff) or safely aborted (FAIL + trace).
  5. Archived: retained for audit and pattern mining.

Two rules keep the Ledger honest:

The easiest way to keep this straight is to split the Mission mentally into two regions:

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.

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:

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:

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.

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:

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:

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 --> [*]
  1. 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).

  2. 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.

  3. 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.

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:

  1. Read node identity from the Context Graph (language + build system).
  2. Look up a driver in a registry (built-in defaults plus repo overrides).
  3. If multiple drivers match, apply precedence (most specific match wins).
  4. 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:

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

Schema Evolution

Bounded Refactor

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

  1. 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 simple constraints.

  2. Mock an AI Executor: Write a small script (Python, Bash, Node) that reads your Mission Object YAML. For now, have it simply print the goal and scope. Then, manually make the requested change in the target file.

  3. Implement a Basic Quality Gate: Add a quality_gate to 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., grep for a specific string, check file size). Have this script output a simple JSON object indicating status (“PASSED” or “FAILED”) and a reason.

  4. Wire up a “Judge”: Modify your executor script from step 2 to call your quality_gate script. 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.

Share