Home / Articles

From Loop Engineering to Graph Engineering: Building Reliable

From loop engineering to graph engineering: the agent loop as an engineering unit, where a single loop breaks down with state tangles, and why graphs help.

Publisher: REACTAPP.TOP22 min read4967 words

This article is published in English.

Sources: From Loop Engineering to Graph Engineering: Building Reliable Agentic Systems in Tech Startups

This walkthrough rebuilds the path from raw materials to a working system for: From Loop Engineering to Graph Engineering: Building Reliable Agentic Systems in Tech Startups. The focus is operable steps, explicit checks, and code that you can drop into a repo without guessing intent. For the Overview stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments.

while not finished:
    context = build_context(state)
    action = model.decide(context)
    observation = tools.execute(action)
    state = update(state, action, observation)

1. The Agent Loop as an Engineering Object

When working through the 1 The Agent Loop stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Checkpoint after expensive steps. Resume should not re-bill the same LLM call when an operator retries a later node.

action = model(current_state)
observation = execute(action)
next_state = transition(current_state, action, observation)

continue_execution =
    task_is_not_complete
    and execution_budget_is_available
MAX_ITERATIONS = 12
MAX_TOOL_CALLS = 30
MAX_RUNTIME_SECONDS = 600
MAX_TOKEN_COST = 250_000

while budgets_available(state):
    action = agent.next_action(state)

    if not policy.allows(action):
        return TerminalFailure("policy_violation")

    result = execute_idempotently(action)
    state = persist_transition(state, action, result)

    verification = verifier.evaluate(state)

    if verification.passed:
        return VerifiedSuccess(
            state=state,
            evidence=verification.evidence
        )

return TerminalFailure("budget_exhausted")

2. Where a Single Loop Starts Breaking

When working through the 2 Where a Single stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Checkpoint after expensive steps. Resume should not re-bill the same LLM call when an operator retries a later node.

State entanglement

When working through the State entanglement stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Checkpoint after expensive steps. Resume should not re-bill the same LLM call when an operator retries a later node. When working through the State entanglement stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments.

Ambiguous completion

The Ambiguous completion stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Keep graph state flat and typed. Nested blobs hide which node wrote which field and break resume after interrupts.

Global retries

The Global retries stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Keep graph state flat and typed. Nested blobs hide which node wrote which field and break resume after interrupts.

Weak fault isolation

The Weak fault isolation stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Keep graph state flat and typed. Nested blobs hide which node wrote which field and break resume after interrupts. The Weak fault isolation stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments.

Privilege aggregation

For the Privilege aggregation stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Put human approval on edges that spend money or change production data. Compile-time wiring does not equal business completeness.

Limited concurrency

For the Limited concurrency stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Put human approval on edges that spend money or change production data. Compile-time wiring does not equal business completeness.

Poor replayability

For the Poor replayability stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Put human approval on edges that spend money or change production data. Compile-time wiring does not equal business completeness. For the Poor replayability stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments.

3. What Graph Engineering Changes

When working through the 3 What Graph Engineering stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Checkpoint after expensive steps. Resume should not re-bill the same LLM call when an operator retries a later node.

Graph:
    nodes
    edges
    shared state
    terminal states
interface Node<Input, Output> {
  id: string;

  inputSchema: Schema<Input>;
  outputSchema: Schema<Output>;

  timeoutMs: number;
  retryPolicy: RetryPolicy;
  requiredCapabilities: Capability[];

  execute(
    input: Input,
    context: RunContext
  ): Promise<Output>;

  verify(
    output: Output,
    context: RunContext
  ): Promise<Evidence>;
}
interface Edge {
  from: NodeId;
  to: NodeId;
  condition: (
    state: WorkflowState
  ) => boolean;
}
const edges = [
  edge(
    "build",
    "tests",
    state => state.build.exitCode === 0
  ),

  edge(
    "build",
    "repair",
    state =>
      state.build.exitCode !== 0 &&
      state.repairAttempts < 3
  ),

  edge(
    "build",
    "failed",
    state =>
      state.build.exitCode !== 0 &&
      state.repairAttempts >= 3
  )
];

4. A Graph for an AI-Assisted Delivery Pipeline

When working through the 4 A Graph for stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Checkpoint after expensive steps. Resume should not re-bill the same LLM call when an operator retries a later node.

                         +----------------+
                         | Threat Model   |
                         +--------+-------+
                                  |
Intake -> Normalize -> Plan ------+-----> Implementation
                     |            |              |
                     |    +-------v--------+     |
                     +--->| Test Planning  |-----+
                          +----------------+     |
                                                 v
                                               Build
                                                 |
                          +----------------------+------------------+
                          | pass                                    | fail
                          v                                         v
                  Integration Tests                         Diagnose -> Repair
                          |                                         |
                     +----+----+                                    +-> Build
                     |         |
                   pass       fail
                     |         |
                     v         +-> Diagnose
                Security Check
                     |
                +----+----+
                |         |
              pass     high risk
                |         |
                v         v
            PR Creation  Human Approval
                |         |
                +----+----+
                     |
                     v
                Canary Deploy
                     |
                Runtime Verify
                  |       |
                pass     fail
                  |       |
                  v       v
                Done   Rollback -> Failed

5. State Must Be Typed, Durable and Partitioned

When working through the 5 State Must Be stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Checkpoint after expensive steps. Resume should not re-bill the same LLM call when an operator retries a later node. When working through the 5 State Must Be stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments.

{
  "runId": "run_93af",
  "goal": {
    "ticketId": "ENG-482",
    "repository": "payments-api",
    "baseRevision": "5df87a1"
  },
  "plan": {
    "artifactId": "sha256:71d...",
    "version": 2
  },
  "implementation": {
    "commit": "81f3ce2",
    "changedFiles": [
      "src/refunds/service.ts",
      "tests/refunds/service.test.ts"
    ]
  },
  "build": {
    "exitCode": 0,
    "artifactId": "sha256:932..."
  },
  "verification": {
    "unitTests": "passed",
    "integrationTests": "passed",
    "security": "pending"
  },
  "budgets": {
    "repairAttempts": 1,
    "toolCalls": 18,
    "tokens": 84320
  }
}
{
  "eventId": "evt_1092",
  "runId": "run_93af",
  "nodeId": "integration-tests",
  "attempt": 2,
  "inputHash": "sha256:8a2...",
  "outputHash": "sha256:19c...",
  "status": "succeeded",
  "startedAt": "2026-09-14T08:41:02Z",
  "completedAt": "2026-09-14T08:43:18Z"
}

6. Evidence-Gated Transitions

The 6 Evidence-Gated Transitions stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Keep graph state flat and typed. Nested blobs hide which node wrote which field and break resume after interrupts.

type TestEvidence = {
  command: string;
  revision: string;
  exitCode: number;
  totalTests: number;
  failedTests: number;
  reportArtifact: ArtifactRef;
};
function testsPassed(
  evidence: TestEvidence,
  workflow: WorkflowState
): boolean {
  return (
    evidence.exitCode === 0 &&
    evidence.totalTests > 0 &&
    evidence.failedTests === 0 &&
    evidence.revision === workflow.currentRevision
  );
}
repository_valid
and build_passed
and tests_passed
and security_accepted
and deployment_verified

7. Artifact Lineage and Selective Replay

The 7 Artifact Lineage and stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Keep graph state flat and typed. Nested blobs hide which node wrote which field and break resume after interrupts.

{
  "artifactId": "sha256:f39...",
  "type": "test_report",
  "producedBy": "integration-tests",
  "graphVersion": "delivery-v4",
  "inputs": [
    "sha256:source-revision-81f3ce2",
    "sha256:test-config-a17"
  ]
}
Source code changed:
    invalidate build artifact
    invalidate unit-test report
    invalidate integration-test report
    invalidate deployment artifact

Threat model unchanged:
    preserve existing threat-model artifact

Requirements unchanged:
    preserve normalized requirements
    preserve approved implementation plan

8. Idempotency and Side-Effect Control

The 8 Idempotency and Side-Effect stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Keep graph state flat and typed. Nested blobs hide which node wrote which field and break resume after interrupts. The 8 Idempotency and Side-Effect stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments.

const idempotencyKey = [
  runId,
  nodeId,
  inputArtifactHash
].join(":");

await deploymentApi.deploy({
  idempotencyKey,
  revision,
  environment
});
deploy_canary -> verify_runtime
                       |
                       +-> failure -> rollback_canary

9. Deterministic and Model-Based Routing

For the 9 Deterministic and Model-Based stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Prefer structured outputs with schema validation over free-form prose when the next step is code or a tool call.

if (testReport.failedTests > 0) {
  return "repair";
}

if (securityReport.criticalFindings > 0) {
  return "human-review";
}

return "deploy";
const decision = await riskClassifier.run({
  changeSummary,
  affectedServices,
  dataClassification,
  migrationPlan
});
type RiskDecision = {
  level: "low" | "medium" | "high";
  reasons: string[];
  requiredChecks: CheckId[];
};

10. Scheduling, Parallelism and Joins

For the 10 Scheduling Parallelism and stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Put human approval on edges that spend money or change production data. Compile-time wiring does not equal business completeness.

all required predecessor nodes are complete
and the incoming transition conditions are satisfied
                 +-> Generate Tests --+
Plan Complete ---+-> Threat Model ----+-> Join -> Implementation Review
                 +-> API Analysis ----+

All

For the All stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Put human approval on edges that spend money or change production data. Compile-time wiring does not equal business completeness. For the All stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments.

Any

When working through the Any stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Checkpoint after expensive steps. Resume should not re-bill the same LLM call when an operator retries a later node.

Quorum

When working through the Quorum stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Checkpoint after expensive steps. Resume should not re-bill the same LLM call when an operator retries a later node.

Best effort

When working through the Best effort stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Checkpoint after expensive steps. Resume should not re-bill the same LLM call when an operator retries a later node. When working through the Best effort stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments.

Fail fast

The Fail fast stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Keep graph state flat and typed. Nested blobs hide which node wrote which field and break resume after interrupts.

11. Failure Semantics

The 11 Failure Semantics stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Keep graph state flat and typed. Nested blobs hide which node wrote which field and break resume after interrupts.

type FailureClass =
  | "transient_dependency"
  | "invalid_input"
  | "verification_failed"
  | "policy_denied"
  | "budget_exhausted"
  | "human_rejected"
  | "unknown";
transient_dependency -> retry with backoff

invalid_input -> return to the upstream producer

verification_failed -> diagnose and repair

policy_denied -> terminal failure

budget_exhausted -> human escalation

human_rejected -> terminal cancellation

unknown -> quarantine and inspect
repair_attempts_remaining
and runtime_budget_available
and token_budget_available
and permitted_blast_radius_not_exceeded

12. Security Boundaries Belong to Nodes

The 12 Security Boundaries Belong stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Keep graph state flat and typed. Nested blobs hide which node wrote which field and break resume after interrupts. The 12 Security Boundaries Belong stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments.

nodes:
  repository_analysis:
    capabilities:
      - repository:read

  implementation:
    capabilities:
      - repository:read
      - workspace:write

  create_pull_request:
    capabilities:
      - repository:pull_request:create

  deploy_canary:
    capabilities:
      - deployment:preview:create

  production_release:
    capabilities:
      - deployment:production:create
    approval:
      required: true

13. Versioning and Reproducibility

For the 13 Versioning and Reproducibility stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Put human approval on edges that spend money or change production data. Compile-time wiring does not equal business completeness.

{
  "runId": "run_93af",
  "graph": {
    "name": "software-delivery",
    "version": "4.2.1"
  },
  "nodes": {
    "planner": "planner@2.3.0",
    "implementation": "coding-agent@5.1.2",
    "security-review": "security-review@1.8.0"
  },
  "models": {
    "planner": "model-a-2026-08",
    "implementation": "model-b-2026-09"
  },
  "policyVersion": "delivery-policy@3.4.0",
  "baseRevision": "5df87a1"
}

14. Observability at Graph Level

For the 14 Observability at Graph stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Put human approval on edges that spend money or change production data. Compile-time wiring does not equal business completeness.

workflow_run_id
graph_version
node_id
node_attempt
parent_node_id
artifact_ids
model_version
prompt_version

15. A Minimal Startup Implementation

For the 15 A Minimal Startup stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Put human approval on edges that spend money or change production data. Compile-time wiring does not equal business completeness. For the 15 A Minimal Startup stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments.

workflow_definitions
workflow_versions
workflow_runs
node_definitions
node_attempts
transition_events
artifacts
approvals
capability_grants
def schedule(run):
    state = load_state(run.id)
    candidates = find_pending_nodes(run.graph_version)

    for node in candidates:
        if dependencies_satisfied(node, state):
            if incoming_guards_allow(node, state):
                enqueue(
                    run_id=run.id,
                    node_id=node.id,
                    graph_version=run.graph_version
                )
def execute_node(job):
    attempt = create_attempt(job)

    try:
        inputs = resolve_inputs(job)
        validate(inputs, job.node.input_schema)

        capabilities = issue_capabilities(job.node)
        output = job.node.execute(inputs, capabilities)

        validate(output, job.node.output_schema)
        evidence = job.node.verify(output)

        persist_success(attempt, output, evidence)
        evaluate_outgoing_edges(job.run_id, job.node.id)

    except Exception as error:
        failure = classify_failure(error)
        persist_failure(attempt, failure)
        apply_failure_transition(job, failure)

    finally:
        revoke_capabilities(job.node)

16. Migrating from a Loop to a Graph

When working through the 16 Migrating from a stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Checkpoint after expensive steps. Resume should not re-bill the same LLM call when an operator retries a later node.

Step 1: Instrument the existing loop

When working through the Step 1 Instrument the stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Checkpoint after expensive steps. Resume should not re-bill the same LLM call when an operator retries a later node.

Step 2: Identify stable stages

When working through the Step 2 Identify stable stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Checkpoint after expensive steps. Resume should not re-bill the same LLM call when an operator retries a later node. When working through the Step 2 Identify stable stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments.

Step 3: Define typed artifacts

The Step 3 Define typed stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Keep graph state flat and typed. Nested blobs hide which node wrote which field and break resume after interrupts.

Step 4: Externalize transitions

The Step 4 Externalize transitions stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Keep graph state flat and typed. Nested blobs hide which node wrote which field and break resume after interrupts.

Step 5: Add independent verification

The Step 5 Add independent stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Keep graph state flat and typed. Nested blobs hide which node wrote which field and break resume after interrupts. The Step 5 Add independent stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments.

Step 6: Introduce checkpoints

For the Step 6 Introduce checkpoints stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Put human approval on edges that spend money or change production data. Compile-time wiring does not equal business completeness.

Step 7: Isolate capabilities

For the Step 7 Isolate capabilities stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Put human approval on edges that spend money or change production data. Compile-time wiring does not equal business completeness.

Step 8: Add bounded recovery edges

For the Step 8 Add bounded stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Put human approval on edges that spend money or change production data. Compile-time wiring does not equal business completeness. For the Step 8 Add bounded stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments.

Step 9: Parallelize proven-independent work

When working through the Step 9 Parallelize proven-independent stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Checkpoint after expensive steps. Resume should not re-bill the same LLM call when an operator retries a later node.

Step 10: Version the graph

When working through the Step 10 Version the stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Checkpoint after expensive steps. Resume should not re-bill the same LLM call when an operator retries a later node.

17. Common Graph Engineering Anti-Patterns

When working through the 17 Common Graph Engineering stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Checkpoint after expensive steps. Resume should not re-bill the same LLM call when an operator retries a later node.

One agent per node

Model-controlled deterministic decisions

Shared mutable state

Unbounded cycles

Agent-reported verification

Global credentials

Missing compensation paths

Graphs without lineage

Premature dynamic routing

18. When a Graph Is Unnecessary

Conclusion

Implicit control flow
becomes
explicit workflow topology

Conversation state
becomes
typed durable state

Agent-declared success
becomes
evidence-gated completion

Global retries
become
bounded local recovery

Shared privileges
become
node-scoped capabilities

Complete reruns
become
dependency-aware replay

Sequential execution
becomes
controlled concurrency

Opaque behavior
becomes
graph-level observability

Operational checklist