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