Build Your First Agent Runtime: Loop → Graph → Hybrid

The companion article:

The New Agent Engineering Stack: Loops, Graphs, Harnesses & Agentmaxxing

explains why agent engineering is moving from prompts to loops, graphs, harnesses, and runtime design.

This tutorial turns those ideas into one small workflow:

Harness → Loop → Graph → Graph + Loop → Parallel Loops

We use Cursor as the concrete worker so every step has real files, commands, skills, sub-agents, and hooks, and after each provider-specific section, we identify the stable principle and show how to carry it to OpenCode, Kilo, Claude Code, Codex, OpenClaw, or another runtime.

Cursor is the example implementation, but the control system still belongs to the repository.

What you will build

A tiny Vite app with three GitHub-style issues:

  1. change the starter page into an “Agent Runtime Lab”
  2. document how to run it
  3. route a security report to a human without starting a writing agent

Then you will add:

The whole system in one diagram

┌──────────────┐
                       ┌────▶│ Human review │────▶ STOP
                       │     └──────────────┘
                       │ security
┌───────┐       ◇──────┴─────◇
│ Issue │──────▶│   Label?    │
└───────┘       ◇──────┬─────◇
                       │ app / docs

              ┌──────────────────┐
              │   Agent loop     │◀────────────┐
              └────────┬─────────┘             │ retry ≤ 2
                       ▼                       │
                ◇──────────────◇      no       │
                │ Build + issue │──────────────┘
                │ contract pass?│
                ◇───────┬──────◇
                        │ yes

              ┌────────────────────┐
              │ Commit + push hook │────▶ DONE
              └────────────────────┘

Other / unlabeled issue ──▶ Needs scope ──▶ STOP

The diamonds are decision nodes; they do not perform open-ended work; rather, they inspect state and select the next legal edge.

Part 1: Start from a template

Prerequisites

You need:

  • Git
  • Node.js 22 or newer
  • Cursor IDE or Cursor Agent CLI
git --version
node --version

Install Cursor, sign in, and confirm the Agent CLI is available:

agent --version

as cursor-specific files are clearly separated from repository-owned contracts.

Create a ready-to-run Vite app

One command creates the TypeScript template:

npm create vite@latest agent-runtime-lab -- --template vanilla-ts
cd agent-runtime-lab

Verify the untouched template:

npm run build

You now have:

agent-runtime-lab/
├── index.html
├── src/
│   ├── main.ts
│   └── style.css
├── package.json
└── tsconfig.json

Initialize Git:

git init -b main
git add -A
git commit -m "chore: initialize Vite TypeScript app"

If you want the later push hook, create an empty remote repository and add it now:

git remote add origin YOUR_REMOTE_URL
git push -u origin main

Create three local GitHub-style issues

mkdir -p issues \
  .cursor/rules \
  .cursor/skills/issue-solver \
  .cursor/agents \
  .cursor/hooks \
  scripts src/runtime .runtime

Add .runtime/ to .gitignoreas, it will hold temporary prompts:

issues/1-app.md:

label: app
title: Replace the Vite starter screen

Change the main heading to `Agent Runtime Lab`.
Add one sentence: `Loops do the work. Graphs decide where work runs.`
Keep the page simple and preserve the Vite build.

Proof:
- `npm run build` exits 0
- `src/main.ts` contains `Agent Runtime Lab`

issues/2-docs.md:

label: docs
title: Document local setup

Update README.md with:
- `npm install`
- `npm run dev`
- `npm run build`

Do not change application source.

Proof:
- `npm run build` exits 0
- README.md contains all three commands

issues/3-security.md:

label: security
title: A contributor reports a possible token leak

Do not inspect, rotate, or invent credentials.
Escalate this issue to a human owner.
No automated write is allowed.

Proof: human review only.

These local files have durable intent, as they survive model changes, chat resets, and provider migrations.

Part 2: Harness engineering

Agent = Model + Harness + Environment
  • The model reasons
  • The environment contains files, Git, npm, and the build command
  • The harness controls context, actions, state, policy, and evidence

Add repository instructions

AGENTS.md

# Agent Runtime Lab

## Commands
- Development: `npm run dev`
- Proof: `npm run build`
- Route an issue: `npm run graph -- issues/<file>.md`
- Solve through the hybrid: `npm run hybrid -- issues/<file>.md`

## Rules
1. Read the issue before editing.
2. Make the smallest change that satisfies it.
3. Run `npm run build` before claiming completion.
4. Check the issue-specific content contract after the build.
5. Never access credentials or use network tools.
6. Security issues are human-only.
7. Never push from `main`; automated pushes are feature-branch only.

We put the real project rules in AGENTS.md so the system works across providers. AGENTS.md is the repository-owned source of truth as it does not belong to Cursor, Claude Code, Codex, or any other host.

.cursor/rules/runtime-policy.mdc

Now Cursor uses rules and Claude has CLAUDE.md. Both should reference AGENTS.md instead of rewriting the system because :

  1. One rule set stays correct for every provider.
  2. Changing providers means updating the thin entry file, not rewriting the whole lab.
  3. Graph, hybrid, proofs, and issue contracts remain repository-owned.
  4. You avoid “cursor-only knowledge” trapped inside .cursor/ that another agent never sees.

Durable instructions stay in the provider-neutral files. Provider files only point to them.

Add one reusable skill

.cursor/skills/issue-solver/SKILL.md:

---
name: issue-solver
description: Use when solving a repository issue with a build and content contract.
---
# Issue solver

1. Read the issue and relevant files.
2. Restate the issue contract in one sentence.
3. Make the smallest relevant change.
4. Run `npm run build`.
5. Check the issue-specific content contract.
6. If either fails, use the output as the next observation.
7. Stop after two failed attempts and report the blocker.

Never claim success from prose alone.

Cursor discovers project skills under .cursor/skills/. It also supports the open .agents/skills/ location, so you can later move the same skill there when several providers share the repository.

Define policy

Create POLICY.md:

# Worker policy

## Allowed
- read repository files
- edit files required by the selected issue
- run `npm run build`
- inspect `git diff` and `git status`

## Denied
- read `.env`, credentials, or private keys
- use network tools
- push or merge
- change protected branches
- perform destructive filesystem operations

## Human-only
- security incidents
- credential handling
- protected-branch writes
- release and deployment approval

Configure Cursor’s approval mode and sandbox to enforce this policy, as Markdown policy alone is guidance, not a security boundary.

AGENTS.md Answers: How should the agent work in this repository?

POLICY.md Answers: What is the agent allowed to do at all?

Why keep them separate?

  1. Different jobs. AGENTS.md is a playbook. POLICY.md is a fence.
  2. Different change rates. Project commands and habits change often. Security boundaries should change carefully.
  3. Easier enforcement. Sandboxes, approval modes, and hooks map cleanly onto allow / deny / human-only lists. They do not map cleanly onto a long operating guide.
  4. Clearer reviews. A teammate can audit the safety fence without reading every workflow tip.
  5. Cross-provider reuse. Another host can keep the same POLICY.md while swapping how it enforces permissions.

Add a evaluator subagent

Create .cursor/agents/evaluator.md:

---
name: evaluator
description: Independently verify an issue solution without editing it
readonly: true
---

# Independent evaluator

Read the selected issue and `git diff`.
Run `npm run build`.
Check the issue-specific content contract.
Do not edit.

Return PASS only when:

1. the build passes
2. the content contract passes
3. the diff matches the issue
4. no invariant was violated

Otherwise return NEEDS_WORK with specific evidence.

In Cursor, project subagents live under .cursor/agents/. Each file defines one agent with a name, description, and instructions.

You can create agents for any provider in that provider’s own directory. For Claude, put them under .claude For Codex, use .codex

The five harness planes

Use the same harness with another provider

Keep AGENTS.md, POLICY.md, the issue files, and the skill body unchanged. Adapt only the discovery layer:

  • point the provider at AGENTS.md
  • place or link SKILL.md in its supported skill directory
  • express POLICY.md through its permission or sandbox controls
  • reuse the evaluator instructions in a fresh read-only worker if custom subagents are unavailable

The filenames may change The five harness planes do not

Part 3: Loop engineering

Most coding agents already contain an inner reason–act loop

observe → reason → use tool → observe → continue

Loop engineering wraps that native cycle with an external goal, evidence, and budget.

Write a strong goal contract

GOAL.md:

# Close issue #1

Read:
- `AGENTS.md`
- `POLICY.md`
- `issues/1-app.md`
- `.cursor/skills/issue-solver/SKILL.md`

## Done when

1. `npm run build` exits 0.
2. `src/main.ts` contains `Agent Runtime Lab`.
3. `src/main.ts` contains `Loops do the work. Graphs decide where work runs.`

## Invariants

- keep the Vite entry point
- add no dependency
- do not edit README
- do not commit or push

## Budget

Stop after 6 turns or 2 failed proof attempts.

Compare:

Bad:  Improve the starter page.
Good: Satisfy three named checks, or stop after two failed attempts.

Run the loop

Open the repository and give your agent this entry prompt:

The agent automatically loads the always-applied project rule and can discover the issue-solver skill. Run the agent in non-interactive mode and permit file modifications but only inside the bounded repository and policy described above.

Verify outside the maker:

npm run build
rg "Agent Runtime Lab" src/main.ts
rg "Loops do the work" src/main.ts
git diff

Then ask the agent to use evaluator.md in a fresh context.

What made this a loop?

Issue → edit → build + contract → failure/success → retry/stop

Use this loop with another provider

Give the other worker the same GOAL.md, AGENTS.md, POLICY.md, issue, and skill. Replace only the invocation command. Its inner tool loop may look different, but the outer goal, evidence, invariants, and budget stay fixed.

Part 4: Graph engineering

A loop is enough for one bounded issue but a graph becomes useful when different issues require different legal paths

app      → application loop
docs     → documentation loop
security → human
other    → scope first

The graph decides this before any writing agent runs.

Build the smallest possible decision graph

Register the graph and hybrid entry points as npm scripts

npm pkg set scripts.graph="tsx src/runtime/graph.ts"
npm pkg set scripts.hybrid="tsx src/runtime/hybrid.ts"

npm pkg set writes those two keys into package.json without opening the file by hand. After this, scripts.graph and scripts.hybrid exist as first-class project commands.

Confirm the scripts landed:

npm run

Install the tiny TypeScript runner and Node.js types:

npm install -D tsx @types/node

@types/node gives TypeScript types for fs, path, and child_process, which the router and hybrid runner use.

Vite’s browser configuration may list only “vite/client” under types and if so, update that line in tsconfig.app.json:

"types": ["vite/client", "node"]

Create src/runtime/graph.ts:

import { readFileSync } from "node:fs";

// These are the only routes the graph is allowed to return.
export type Route = "app" | "docs" | "human" | "scope";

export function routeIssue(issuePath: string): Route {

  const issue = readFileSync(issuePath, "utf8");
  const label = issue.match(/^label:\s*(\w+)/m)?.[1];

  // Convert the label into one explicit, legal edge.
  if (label === "app") return "app";
  if (label === "docs") return "docs";

  // Security work is deliberately blocked from automated writers.
  if (label === "security") return "human";

  // Unknown or missing labels need a clearer contract before automation.
  return "scope";
}

// Run this CLI entry point only when graph.ts is executed directly.
if (import.meta.filename === process.argv[1]) {
  const issuePath = process.argv[2];
  if (!issuePath) throw new Error("Pass an issue path.");
  console.log({ issue: issuePath, route: routeIssue(issuePath) });
}

Start and execute the graph

This graph is a route-only program to showcase the working of how graphs work internally, so it reads an issue, prints the selected route, and exits, but it does not start Vite, open a browser, call a model, or edit a file.

cd agent-runtime-lab
npm install
npm run graph -- issues/1-app.md

The command has four parts:

npm run graph  → run the package.json "graph" script
--             → forward the remaining argument to graph.ts
issues/1-app.md→ process.argv[2], the issue file to route

Now run all three decisions:

npm run graph -- issues/1-app.md
npm run graph -- issues/2-docs.md
npm run graph -- issues/3-security.md

app result does not run an app worker yet, the docs result does not edit the README; the human result does not contact a reviewer, as this stage only proves that the routing policy works

No model call No provider API No token spend

How to read a decision node

yes ──▶ next node A
input ──▶ ◇ condition? ◇
                 no  ──▶ next node B

A decision node should:

  1. read existing state
  2. select a legal edge
  3. avoid doing open-ended work itself

In this graph, routeIssue() reads the label and selects one of four edges. A coding agent appears only after the graph chooses app or docs.

Think of two ways to control an agent workflow

Soft graph

A soft graph is a set of steps written in Markdown, skills, or prompts

Example:

1. Read the issue
2. Edit the files
3. Run the build
4. Check the result
5. Stop when it passes

Use a soft graph for low-risk work:

  • update a README
  • rename a heading
  • fix a small lint issue
  • write a draft that a human can review later

Hard graph

A hard graph puts the legal paths into code.

Example:

app      → allow a coding worker
docs     → allow a docs worker
security → stop and wait for a human
other    → stop until the issue has a clear label

The system, not the model, chooses the next legal edge. If the route is security, the worker never starts. That is human-in-the-loop (HITL): the graph pauses and waits for a person before any risky write can happen.

Use a hard graph for harder or higher-risk work:

  • security incidents
  • credential handling
  • money movement
  • production deploys
  • anything that needs an approval gate before automation continues

Part 5: Agent behind a provider adapter

What problem this part solves

We have already built a router. The router only prints app, docs, or humanbut it never starts the agent.

Now we will build the hybrid runner, but that runner needs a way to start a coding agent after the graph chooses app or docs

Different coding agents use different commands:

Cursor:      agent -p --force "..."
Claude Code: claude ...
Codex:       codex ...
OpenCode:    opencode ...

If we put Cursor’s flags directly inside hybrid.ts, the whole runtime becomes Cursor-only and changing providers would mean editing the graph and verifier too.

So we create one thin adapter file first

AGENT RUNNER

AGENT_RUNNER is a simple contract:

AGENT_RUNNER <prompt-file>
  1. Call one executable.
  2. Give it one argument: the path to a prompt file.
  3. That executable starts whatever coding agent we chose.

In this lab, the executable is scripts/run-agent

AGENT_RUNNER          = scripts/run-agent   (or another adapter)
<prompt-file>         = .runtime/worker-prompt.md

When we create it, and when we run it

Execution order for a docs issue:

npm run hybrid -- issues/2-docs.md
  → hybrid.ts routes the issue to "docs"
  → hybrid.ts writes .runtime/worker-prompt.md
  → hybrid.ts runs: scripts/run-agent .runtime/worker-prompt.md
  → scripts/run-agent starts Cursor Agent CLI
  → Cursor edits the repo
  → hybrid.ts checks build + content contract

You do not need to type scripts/run-agent yourself during normal use. Hybrid calls it.

Implement the adapter

Create scripts/run-agent:

#!/usr/bin/env bash

# Exit on an error, an unset variable, or a failed command in a pipeline.
set -euo pipefail

# The hybrid runtime passes one generated prompt file to this adapter.
prompt_file="${1:?pass a prompt file}"
prompt=$(<"$prompt_file")

# Replace this final command to use another provider.
exec agent -p --force "$prompt" --output-format text

Make it executable:

chmod +x scripts/run-agent

Optional smoke check, if you want to prove the script starts:

echo "Say hello and stop. Do not edit files." > /tmp/agent-smoke.md
./scripts/run-agent /tmp/agent-smoke.md

That is the first time the adapter is used as part of the full system.

The hybrid runtime now has a concrete worker without importing details into the graph or verifier.

Use another provider

Keep the same contract and replace only the final invocation inside the adapter:

#!/usr/bin/env bash
set -euo pipefail

prompt_file="${1:?pass a prompt file}"
prompt=$(<"$prompt_file")

exec YOUR_AGENT_COMMAND YOUR_NON_INTERACTIVE_FLAG "$prompt"

OpenCode, Kilo, Claude Code, Codex, and OpenClaw do not necessarily share Cursor’s -p, —force, or output flags so read the chosen runtime’s current documentation and map it to this one-input contract. The graph and hybrid runner require no changes.

Part 6: Hybrid: graph + loop

Now the graph chooses whether a worker may run, and then the selected worker retries under a small budget.

Graph owns policy
  └── Provider adapter owns invocation
        └── Coding agent owns open-ended implementation
              └── Outer runtime owns proof and retries

12. Add the hybrid runner

Create src/runtime/hybrid.ts:

import { spawnSync } from "node:child_process";
import {
  mkdirSync,
  readFileSync,
  writeFileSync,
} from "node:fs";
import { resolve } from "node:path";
import { routeIssue } from "./graph.ts";

// Only these routes are allowed to start an automated worker.
type WorkerRoute = "app" | "docs";

// A hard budget prevents an unsuccessful worker from running forever.
const MAX_ATTEMPTS = 2;

function buildPasses(): boolean {
  // Run the proof outside the coding agent.
  // Inheriting stdio keeps the build evidence visible in the terminal.
  return spawnSync("npm", ["run", "build"], {
    stdio: "inherit",
  }).status === 0;
}

function contractPasses(route: WorkerRoute): boolean {
  if (route === "app") {
    // A passing build alone cannot prove that the requested content exists.
    const source = readFileSync("src/main.ts", "utf8");
    return (
      source.includes("Agent Runtime Lab") &&
      source.includes("Loops do the work")
    );
  }

  // The docs route has a different acceptance contract.
  const readme = readFileSync("README.md", "utf8");
  return ["npm install", "npm run dev", "npm run build"].every((command) =>
    readme.includes(command),
  );
}

function writePrompt(issuePath: string, route: WorkerRoute): string {
  // Generated prompts are temporary runtime state, not source files.
  mkdirSync(".runtime", { recursive: true });
  const promptPath = resolve(".runtime/worker-prompt.md");

  // Keep the entry prompt thin. Durable rules, policy, procedure, and
  // acceptance criteria remain in repository-owned files.
  const prompt = `# Worker task

Read:
- AGENTS.md
- POLICY.md
-${issuePath}
- .cursor/skills/issue-solver/SKILL.md

Make the smallest${route} change.
Run npm run build and check the issue contract.
Do not commit, push, or use network tools.
`;

  writeFileSync(promptPath, prompt);

  // The provider adapter accepts a prompt-file path, not provider-specific state.
  return promptPath;
}

function runWorker(issuePath: string, route: WorkerRoute): void {
  // AGENT_RUNNER can replace Cursor without changing graph or verifier logic.
  const runner = process.env.AGENT_RUNNER ?? "scripts/run-agent";

  // spawnSync waits for the worker to finish before verification begins.
  const result = spawnSync(runner, [writePrompt(issuePath, route)], {
    stdio: "inherit",
  });

  // A missing or unstartable adapter is a runtime error, not a failed contract.
  if (result.error) throw result.error;
}

// npm forwards the issue path after "--" into process.argv[2].
const issuePath = process.argv[2];
if (!issuePath) throw new Error("Pass an issue path.");

// Route first. No worker prompt or provider process exists before this decision.
const route = routeIssue(issuePath);

if (route === "human") {
  // Security issues stop before any automated write can begin.
  console.log("STOP: human security review required.");
  process.exit(0);
}

if (route === "scope") {
  // Unknown work stops until a human supplies a label and acceptance contract.
  console.log("STOP: issue needs a label and contract.");
  process.exit(0);
}

// Only "app" and "docs" can reach this bounded worker loop.
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
  console.log(`Attempt${attempt}/${MAX_ATTEMPTS}`);
  runWorker(issuePath, route);

  // The outer runtime, not the maker, decides whether evidence is sufficient.
  if (buildPasses() && contractPasses(route)) {
    console.log("PASS: build and issue contract succeeded.");
    process.exit(0);
  }
}

console.error("FAILED: retry budget exhausted.");
process.exit(1);

Start and execute the hybrid runtime

Unlike the route-only graph, the hybrid command starts a real agent and allows it to edit the current repository.

Understand the execution path

npm run hybrid -- issues/2-docs.md

starts this exact sequence

1. npm runs tsx src/runtime/hybrid.ts
2. "--" forwards issues/2-docs.md to process.argv[2]
3. routeIssue() reads label: docs
4. the graph allows the docs worker route
5. writePrompt() creates .runtime/worker-prompt.md
6. scripts/run-agent passes that prompt to Cursor Agent CLI
7. Cursor Agent reads the repository artifacts and edits README.md
8. the outer runtime runs npm run build
9. the outer runtime checks the docs content contract
10. PASS exits 0; failure starts attempt 2; two failures exit 1

Execute an automated route

Run the documentation issue:

npm run hybrid -- issues/2-docs.md

The terminal should first print Attempt 1/2, then show the Agent output, the Vite build, and finally either:

PASS: build and issue contract succeeded.

or, after both attempts fail:

FAILED: retry budget exhausted.

The Cursor adapter runs .runtime/worker-prompt.md through Agent CLI.

Execute a blocked route

Now prove that the security route stops before a worker starts:

npm run hybrid -- issues/3-security.md

Expected:

STOP: human security review required.

You should not see Attempt 1/2, a generated worker prompt, or Cursor Agent output. That absence is the proof that routing happened before execution.

Why this is hybrid

The graph is the map The loop is the engine inside selected nodes The provider is replaceable

Part 7: A build-success hook

The desired flow is:

npm run build succeeds
  → git add
  → git commit
  → git push

For Cursor we use project-level postToolUse hook, filter for the Shell tool, then inspect the command and exit code in a small script.

Create the shared publish script

Create scripts/publish-after-build.sh:

#!/usr/bin/env bash
set -euo pipefail

if [ ! -f ".cursor/auto-push.enabled" ] &&
   [ "${AUTO_PUSH_AFTER_BUILD:-0}" != "1" ]; then
  echo "Auto-push disabled."
  exit 0
fi

root=$(git rev-parse --show-toplevel)
cd "$root"

branch=$(git branch --show-current)
case "$branch" in
  ""|main|master)
    echo "Refusing automatic push from protected branch:${branch:-detached HEAD}"
    exit 0
    ;;
esac

if !git remote get-url origin >/dev/null 2>&1; then
  echo "No origin remote; skipping commit and push."
  exit 0
fi

if git diff --quiet && git diff --cached --quiet; then
  echo "Build passed, but there are no changes to publish."
  exit 0
fi

git add -A

if git diff --cached --quiet; then
  echo "Nothing staged after git add."
  exit 0
fi

git commit -m "${AUTO_COMMIT_MESSAGE:-chore(agent): publish successful build}"
git push -u origin "$branch"

Make it executable:

chmod +x scripts/publish-after-build.sh

Add the hook adapter

Create .cursor/hooks/after-successful-build.mjs:

import { spawnSync } from "node:child_process";

let raw = "";
for await (const chunk of process.stdin) raw += chunk;

try {
  const event = JSON.parse(raw);
  const command = event.tool_input?.command?.trim();
  const output =
    typeof event.tool_output === "string"
      ? JSON.parse(event.tool_output)
      : event.tool_output;

  if (command !== "npm run build" || output?.exitCode !== 0) {
    console.log("{}");
    process.exit(0);
  }

  const result = spawnSync(
    "bash",
    ["scripts/publish-after-build.sh"],
    {
      cwd: event.cwd ?? process.cwd(),
      encoding: "utf8",
    },
  );

  if (result.stdout) process.stderr.write(result.stdout);
  if (result.stderr) process.stderr.write(result.stderr);

  const message =
    result.status === 0
      ? "The build passed and the guarded publish hook completed."
      : "The build passed, but the guarded publish hook failed.";

  console.log(JSON.stringify({ additional_context: message }));
} catch (error) {
  console.error(error);
  console.log(JSON.stringify({
    additional_context: "The build hook could not parse its Cursor event.",
  }));
}

Create .cursor/hooks.json:

{
  "version": 1,
  "hooks": {
    "postToolUse": [
      {
        "command": "node .cursor/hooks/after-successful-build.mjs",
        "matcher": "Shell",
        "timeout": 120
      }
    ]
  }
}

Cursor watches .cursor/hooks.json and reloads it when saved. If it does not appear, check Customize → Hooks or the Hooks output channel.

Two practical constraints:

  • Agent hooks fire for shell tools run through Cursor Agent or Cmd+K, not for every command typed in an ordinary terminal.
  • Project hooks require a trusted workspace.

Enable the side effect deliberately

Ignore the local opt-in marker:

.cursor/auto-push.enabled

Then enable it on a feature branch:

git switch -c agent/docs-issue
touch .cursor/auto-push.enabled

Now ask Cursor Agent to make the docs change and run npm run build. When the build succeeds, the hook:

  1. refuses main, master, and detached HEAD
  2. confirms an origin remote exists
  3. skips empty changes
  4. stages all changes
  5. commits
  6. pushes the current feature branch

Disable it immediately after the run:

rm .cursor/auto-push.enabled

Important limitation

The Cursor hook fires immediately after a successful build, so it cannot know whether the requested page or documentation is correct.

For a real project, publish after a stronger command such as

build → lint → tests → issue contract → review → publish

Also audit .gitignore before using git add -A. Never allow generated secrets, credentials, or .env files into the repository, and remember that a broad git add -A can also stage unrelated local changes.

Use the hook with another provider

Keep scripts/publish-after-build.sh; replace only the event adapter.

  • If the provider has hooks, connect its successful shell/tool event to the script and check both the exact command and exit status.
  • If it has no hooks, use npm’s portable lifecycle:
npm pkg set scripts.postbuild="bash scripts/publish-after-build.sh"
AUTO_PUSH_AFTER_BUILD=1 \
AUTO_COMMIT_MESSAGE="docs: close setup issue" \
npm run build

postbuild runs only after build succeeds. Do not enable both the Cursor hook and npm postbuild, or the publish script will be invoked twice.

Part 8: Agentmaxxing

Agentmaxxing means several bounded loops running in parallel in isolated worktrees but it does not mean several agents editing one directory.

Issue #1 and Issue #2 are independent

git worktree add ../agent-runtime-app -b agent/app
git worktree add ../agent-runtime-docs -b agent/docs

Terminal A:

cd ../agent-runtime-app
npm install
npm run hybrid -- issues/1-app.md

Terminal B:

cd ../agent-runtime-docs
npm install
npm run hybrid -- issues/2-docs.md

The workers may use the same provider, different providers, different models, or a human-agent pair. The branch contract and proof remain identical.

The join gate

Before merging:

git diff main...agent/app
git diff main...agent/docs

After integrating both branches:

npm run build

Worktrees prevent file collisions, but they do not guarantee that two independently valid changes work together. Parallelism is optional and should remain bounded by cost, conflict risk, and review capacity.

Part 9: Execution, memory, and optional specs

E/M in this project

For longer tasks, add PROGRESS.md:

# Progress

| Issue | Attempt | Build | Contract | Note |
| --- | --- | --- | --- | --- |
| 1 | 1 | PASS | FAIL | heading text missing |

Do not rely on any provider’s chat history for information that must survive compaction, a new session, or a runtime migration

Optional SPEC.md

You do not need a spec for one heading change. Add one when several workers or sessions must share the same interpretation:

Implement a spec while logging decisions to HTML

As you work, maintain a running `implementation-notes.html` file in
the same directory that captures anything I should know about how
the implementation diverges from or interprets the spec, including:

- Design decisions: choices you made where the spec was ambiguous
- Deviations: places where you intentionally departed from the spec, and why
- Tradeoffs: alternatives you considered and why you picked what you did
- Open questions: anything you'd want me to confirm or revise

Use real HTML structure: tables for tradeoffs, headings for sections,
collapsible details elements for the open questions block. Make it
readable in a browser, not just in a code editor.

Thin prompts, thick artifacts, thin provider adapters.

Part 10: Moving the example anywhere

Cursor gave us a concrete rule, skill, evaluator, CLI adapter, and hook. To move the workflow, preserve the repository-owned contracts and translate only provider integration

Do not assume identical capabilities. A provider may support subagents, hooks, sandboxes, skills, schedules, or background execution differently. Map those features onto stable roles

Intent     → issue / goal / optional spec
Procedure  → skill
Policy     → graph + permissions
Worker     → provider adapter
Evidence   → build + contract + evaluator
Memory     → files + Git + checkpoints
Side effect→ guarded hook or human approval

Upgrade paths

Frameworks can help later. First learn the responsibilities in plain files, TypeScript, npm, and Git.

Reliability checklist

Common failure modes

Try a real GitHub issue later

Once the local workflow works, fetch a real issue using the GitHub CLI, API, MCP tool, or another integration:

gh issue view 12 \
  --repo YOUR_NAME/YOUR_REPO \
  --json number,title,body,labels

Convert the result into the same local issue contract, then run the graph.

Keep these actions human-approved:

  • posting comments
  • changing labels
  • closing an issue
  • opening or merging a pull request
  • pushing protected branches

Conclusion

Coding-agent providers already supply the inner reason–act loop. Loop engineering gives that worker a goal, evidence, and a budget. Graph engineering decides where those loops may run. Harnesses, skills, adapters, memory, hooks, and worktrees make the system repeatable.

Start from a working template. Add one issue and one contract. Route before automating. Keep side effects guarded. The durable knowledge is not a provider command — it is the ability to design a runtime whose decisions, evidence, and boundaries stay understandable.