Skip to content
masterclassBuddyTech Buzz5 fresh blogs · 5:00 AM IST

LangGraph blueprint · Developer tooling

Self-correcting code generation loop

A LangGraph StateGraph generates code, runs its tests in an isolated sandbox, reflects on failures and retries within a fixed attempt budget. A conditional edge routes each result to pass, retry or give up, and a Postgres checkpointer saves every attempt. It must never ship code that did not pass the real tests, and it must never loop without a bound.

Steps
8
Subjective questions
33
Objective questions
33

The problem and the outcome

Problem: One-shot LLM code generation often fails on edge cases, imports or API misuse, and developers lose time pasting errors back by hand. Ad-hoc retry scripts tend to loop forever, burn tokens and keep no record of what was tried.

Outcome: Each task becomes a durable thread with a capped number of generate, test and reflect cycles. Passing code comes with test evidence, failed tasks go to a human with the full attempt history, and any attempt can be replayed or forked.

Architecture

Developertask + test spec01Postgres saverper super-step08Generate nodecode attempt02PostgresStorepast fix lessonsSandbox runnerisolated tests03route_after_testspass/retry/give up04Reflect nodecritique failure05Escalate nodeinterrupt()07Finalize nodepatch + evidence06Human reviewerhint or abortStream updatesto developer UItask, thread_idcode + testsexit code, logsall tests passfail, budget leftcritique + fix planbudget exhaustedpause for reviewCommand(resume=...)similar past fixessave lessonstate per stepfinal patchupdates mode

How it works, step by step

  1. The developer submits a task, target files and a test command. The service starts a new thread_id, so every attempt for this task lives in one checkpointed thread.
  2. The generate node asks a code-capable LLM for a full candidate solution. It uses the task, the latest critique and relevant lessons from PostgresStore, and appends the attempt to state.attempts through an operator.add reducer.
  3. The sandbox tool writes the code into a fresh, network-restricted container with CPU, memory and time limits. It runs the real test command and returns the exit code plus truncated stdout, stderr and failing test names.
  4. The conditional edge route_after_tests reads state and returns one of three node names. It returns finalize if tests pass, reflect if they fail and iteration < max_iterations, and escalate otherwise.
  5. The reflect node asks the LLM to diagnose the failure from the logs, name the root cause and propose a concrete change. It increments the iteration counter and stores the critique, then a normal edge sends control back to generate.
  6. The finalize node packages the passing diff with the test output as evidence. It writes a short lesson (error pattern and fix) to PostgresStore under a namespace like (repo, 'fix_lessons') and streams the result to the developer.
  7. When the budget is spent, the escalate node calls interrupt() with the last code, the logs and a summary of every attempt. A reviewer either resumes with Command(resume=...) carrying a hint, which grants extra iterations, or aborts the task.
  8. The Postgres checkpointer saves state at every super-step, so a crashed run resumes from its last step. Engineers use get_state_history to inspect any attempt and update_state to fork from it with a corrected prompt or a different model.

Tech stack

  • LangGraph StateGraph with TypedDict state and operator.add reducers
  • Conditional edges plus START/END for pass, retry and give-up routing
  • PostgresSaver checkpointer in production, InMemorySaver in dev
  • PostgresStore for cross-thread fix lessons with semantic search
  • Container-based sandbox (e.g. gVisor or Firecracker class isolation) with resource limits
  • interrupt() and Command(resume=...) for human escalation
  • LangSmith tracing and Agent Server deployment with streaming

Key design decisions

  • Bound the loop twice: a max_iterations counter in state that the router checks, plus the graph recursion_limit as a hard backstop if routing logic has a bug.
  • Make the sandbox the source of truth. The router decides only from real exit codes and test results, never from the LLM's own claim that the code works.
  • Keep reflect as a separate node from generate so critiques are inspectable in checkpoints and can use a cheaper or different model than generation.
  • Truncate and structure test logs (failing test names, first error, traceback tail) before putting them in state, to control token cost and keep checkpoints small.
  • Escalate with interrupt() rather than silently failing, so a human gets the full attempt history and can resume the same thread instead of starting over.

Common mistakes

  • Letting the LLM edit or delete the tests to make them pass. Keep the test files read-only in the sandbox and diff-check that they are untouched.
  • Running generated code with network access or host mounts, which turns a coding loop into a remote code execution risk.
  • Storing whole stdout dumps in state on every attempt, which bloats Postgres checkpoints and the prompt context.
  • Retrying with the same prompt and temperature each time, so the model repeats the same mistake. Feed the critique and the previous diff explicitly.
  • Treating flaky tests as code failures. Rerun failures once and mark tests as flaky before spending a retry.

What to measure

  • First-attempt pass rate and pass rate within the iteration budget
  • Mean attempts to pass and the distribution of give-up reasons
  • Escalation rate and reviewer resolution time
  • Token cost and sandbox runtime per completed task
  • Rate of attempts blocked for touching protected test files

Most-asked interview questions

Subjective questions 33

1How do you stop a self-correcting code loop from running forever?Asked almost every time

I use two layers. First, the state carries an iteration counter and a max_iterations value. The reflect node increments the counter, and the conditional edge route_after_tests checks it before choosing reflect or escalate. That is the business rule. Second, I set the graph's recursion_limit as a hard backstop, so a bug in routing raises an error instead of burning tokens. I also cap cost per thread and per sandbox run with time and memory limits. When the budget runs out, the graph does not just end silently. It routes to an escalate node that calls interrupt(), so a human sees every attempt and can resume the same thread with a hint and a small extra budget.

Sourced from Docs by LangChain (MCP)

2Walk me through this self-correcting code generation workflow end to end.Asked almost every time

A developer submits a task, target files and a test command, and the service opens a new thread_id so every attempt lives in one checkpointed thread. The generate node asks a code-capable LLM for a full solution, using the task, the latest critique and relevant lessons from PostgresStore. It appends the attempt to state.attempts. A sandbox tool runs the real tests in a fresh, network-restricted container with resource limits. The conditional edge route_after_tests then sends control to finalize on a pass, to reflect on a failure while budget remains, and to escalate otherwise. Reflect diagnoses the failure, increments the iteration counter and loops back to generate. Finalize packages the diff with test evidence and writes a lesson. Escalate interrupts for a human reviewer. Postgres checkpoints every super-step.

Sourced from Docs by LangChain (MCP)

3What would you put in the graph state, and which keys need reducers?Asked almost every time

I would use a typed state with these keys: the task description, the target files, the test command, attempts, latest_critique, iteration, max_iterations, last_test_result and final status. attempts is a list annotated with operator.add, so each generate call appends rather than replaces. Each attempt records the code, the model used and its test result. iteration, max_iterations and latest_critique are plain overwrite keys, because only the newest value matters. last_test_result holds the exit code, the truncated stdout and stderr, and the failing test names. I keep the generated code in the attempt entries and not duplicated elsewhere, so checkpoints stay smaller. Clear typing matters because the routing function reads these fields directly. A missing or wrongly typed field should fail loudly, not route silently.

Sourced from Docs by LangChain (MCP)

4How would you design the sandbox so untrusted generated code cannot harm your systems?Asked almost every time

Treat every candidate as hostile. Each run gets a fresh container that is never reused across attempts. Network access is restricted by default. CPU, memory, wall-clock time and process limits are enforced, and the filesystem is read-only except for a scratch workspace. The container runs as a non-root user with dropped capabilities and no access to the host Docker socket. It holds no production credentials or customer data. The only inputs are the repo snapshot and the candidate code. The only outputs are the exit code, truncated logs and failing test names. After the run the container is destroyed. For stronger isolation, I would use a sandboxed runtime or microVM, because shared-kernel containers have a larger escape surface.

Sourced from Docs by LangChain (MCP)

5How do you stop the model from passing by gaming the tests, for example editing or deleting them?Asked almost every time

The rule is that code ships only after the real tests pass, so the tests must be outside the model's control. The test files and the test command come from the developer's request and the repo snapshot, not from the model's output. Before running, the sandbox restores test files from the protected snapshot, or rejects any diff that touches test paths, config or CI files. It also checks that the expected tests were collected and ran, so 'zero tests ran' is not treated as a pass. Finalize attaches the test output as evidence, and the reviewer looks at the diff in a normal code review. Hard-coded outputs are caught by meaningful tests and by human review.

Sourced from Docs by LangChain (MCP)

6What are the main security risks beyond sandbox escape, and how do you address them?Asked almost every time

Prompt injection is the big one. Repo files, issue text and test output all go into the prompt, and any of them could contain instructions like 'delete the tests' or 'print environment variables'. Treat all of it as data, and enforce rules in code, not prompts: protected test paths, a sandbox with no secrets and no network, and a diff policy check before finalize. Second, secrets in the repo. Scan and redact them before sending content to the LLM and before writing checkpoints or lessons. Third, supply chain risk. The generated code might add dependencies, so use a pinned, allow-listed package mirror. Finally, apply access control on threads, so only authorised users can read or resume them.

Sourced from Docs by LangChain (MCP)

7Why is a checkpointer important in this workflow, and what do you get from it beyond crash recovery?Very often asked

The checkpointer saves state per thread_id at every super-step, so each generate, test and reflect step is a durable snapshot. Crash recovery is the obvious benefit: if a worker dies during a long sandbox run, we resume from the last checkpoint instead of regenerating. The bigger value is debugging and evaluation. With get_state_history I can open attempt three, see the exact code, logs and critique, and understand why the model went wrong. With update_state I can fork from that point with a better prompt or a different model and compare outcomes. Checkpoints are also what make interrupt() work, because the paused human-review step has to survive for hours. In production I use the Postgres saver and keep state small by truncating logs.

Sourced from Docs by LangChain (MCP)

8How do you make sure the code that passes is actually correct and safe to merge?Very often asked

First, the sandbox decides, not the model. The router reads real exit codes and parsed test results. Second, I protect the tests: test files are mounted read-only, and the finalize node checks that the diff does not touch them, because models will sometimes weaken assertions to get a green run. Third, I add cheap extra gates in the same sandbox run, such as linting, type checking and a security scan, so passing means more than unit tests. Fourth, the sandbox has no network and no host mounts and runs under resource limits, so a malicious or buggy attempt cannot do harm. Finally, finalize attaches the test output as evidence, and the change still goes through normal code review before merge.

Sourced from Docs by LangChain (MCP)

9Why model this as a LangGraph StateGraph instead of a plain while loop in Python?Very often asked

A while loop works until something goes wrong. With a StateGraph, each step is a node with a typed state, and the pass, retry and give-up decisions are an explicit conditional edge you can read and test. The bigger gain is the checkpointer. State is saved at every super-step, so a crashed run resumes from its last step instead of starting over. You can inspect any past attempt with get_state_history and fork from it with update_state. interrupt() gives you a clean human pause and resume on the same thread. Streaming modes let the developer watch progress. You could build all of this by hand around a loop, but you would end up rewriting durable execution, and you would get it slightly wrong.

Sourced from Docs by LangChain (MCP)

10Why does the routing decision live in a conditional edge rather than inside the sandbox node?Very often asked

Keeping route_after_tests as a separate conditional edge splits doing from deciding. The sandbox node does one job: run the tests and report facts such as the exit code and the failing tests. The edge is a small pure function of state that returns finalize, reflect or escalate. That makes it trivial to unit test against every boundary, for example iteration equal to max_iterations. It also shows up clearly in the graph diagram, so reviewers can see that every path ends. You could use Command(goto=...) from inside the node instead, which is fine for handoffs. Here, though, a pure routing function is easier to audit for the core safety rule that only passing code reaches finalize.

Sourced from Docs by LangChain (MCP)

11How do you deal with flaky tests in this loop?Very often asked

Flaky tests are a problem in both directions. A false failure burns retries and sends the model chasing phantom bugs. A false pass breaks the 'only real passes ship' rule. On failure, I rerun just the failing tests once or twice in a fresh sandbox before reflecting. If results differ, I mark the test as flaky in the state and escalate instead of letting the model rewrite working code. On a pass, I can require the full suite to pass in a clean run inside finalize before packaging. I also track per-test flake history in a store, so known flaky tests are surfaced to the reviewer rather than hidden.

Sourced from Docs by LangChain (MCP)

12How does the PostgresStore lesson memory work, and what can go wrong with it?Very often asked

When finalize succeeds, it writes a short lesson, the error pattern and the fix, under a namespace like (repo, 'fix_lessons'). Generate later searches that namespace, optionally with semantic search, and adds the most relevant lessons to its prompt. Because stores work across threads, a fix learned on one task helps the next. The risks are bad or stale lessons. A lesson from a fix that passed weak tests can mislead future attempts. A lesson that mentions an old API outlives a refactor. Lessons can also leak sensitive code across repos if namespaces are wrong. Mitigations: only write lessons from real passes, keep namespaces scoped per repo, add timestamps and expiry, cap how many lessons are injected, and let engineers delete bad ones.

Sourced from Docs by LangChain (MCP)

13How would you scale this to many developers submitting tasks at the same time?Very often asked

Each task is an independent thread, so the graph scales horizontally. Run stateless workers that pick up runs and keep all state in the Postgres checkpointer. The real bottlenecks are sandbox capacity and LLM rate limits, not the graph itself. I would put sandbox runs behind a queue with a concurrency cap and per-tenant quotas, so one team cannot starve others. LLM calls need rate-limit-aware retries with backoff. Postgres needs connection pooling and indexes on thread_id. To size capacity, multiply the expected concurrent tasks by average attempts per task, then by average sandbox duration and resources. Measure these numbers from real traffic rather than guessing.

Sourced from Docs by LangChain (MCP)

14If the worker crashes while the sandbox is running tests, what happens on resume?Very often asked

The checkpointer saved state after the last completed super-step, so on resume the sandbox step runs again from that point. Its result was never recorded. That means the sandbox step must be safe to repeat. It is, because each run uses a fresh container and only reports results. I would also make sure orphaned containers from the crashed run are cleaned up by a timeout or a reaper keyed on run ID. LLM calls in generate are also re-executed if they did not complete, which costs tokens but is correct. Any side effect outside the graph, such as opening a pull request, should happen only in finalize and use an idempotency key.

Sourced from Docs by LangChain (MCP)

15How does the escalation and reviewer resume flow work in detail?Very often asked

When route_after_tests sees a failure with iteration at max_iterations, it sends control to escalate. That node calls interrupt() with the last code, the logs and a summary of every attempt. The run pauses and the state is checkpointed under the same thread_id. A reviewer can resume with Command(resume=...) carrying a hint. The escalate node receives that value, stores it as the critique and raises max_iterations by a small fixed amount, so the loop has a new but still finite budget. It then routes back to generate. Alternatively, the reviewer aborts, and the node sets the status to aborted and ends. Either way, no unverified code ships.

Sourced from Docs by LangChain (MCP)

16How would you choose max_iterations?Very often asked

Pick it from data, not instinct. Run the loop on a representative set of past tasks with a generous budget, and record the attempt number at which each task first passed. Usually most successes come early, and later attempts add little while cost and latency keep rising. Choose the point where the marginal pass rate no longer justifies the cost, and make it configurable per repo or task type. Pair it with a wall-clock limit and a token budget, because iteration count alone does not bound cost if each attempt is expensive. Review the choice periodically as models and repos change.

Sourced from Docs by LangChain (MCP)

17Besides the iteration counter, what other budgets would you enforce?Very often asked

The iteration counter bounds loop length, but not the cost of each step. I would add a total token budget per task, tracked in state and checked by the router, so a run that exceeds it escalates. I would add a wall-clock deadline for the whole task, plus the sandbox's own CPU, memory and time limits per run. LLM calls get request timeouts and a capped number of transport retries. Escalations get a cap on how many extra iterations a reviewer can grant. Interrupted threads waiting for review get a timeout after which they are aborted. Each of these makes the system bounded in a different way.

Sourced from Docs by LangChain (MCP)

18What would you monitor in production?Very often asked

Track outcomes and costs together. For outcomes: the pass rate, the distribution of attempts to pass, the escalation rate, the reviewer abort rate and the share of escalations resolved by a hint. For costs: tokens and sandbox time per task. For health: sandbox failures split into test failures, timeouts, out-of-memory kills and infrastructure errors, plus LLM error and rate-limit rates, checkpoint write latency and threads stuck in interrupt. For quality: the post-merge revert rate of generated diffs and how often lessons are retrieved and helpful. Tracing each node with thread_id lets you jump from a bad metric to the exact attempt using get_state_history.

Sourced from Docs by LangChain (MCP)

19How do you tell a code failure apart from an environment or infrastructure failure?Very often asked

If you get this wrong, the model rewrites correct code to fix a broken environment. The sandbox should return a structured result, not just an exit code. It should separate tests that ran and failed from cases such as the container being killed for memory or time, the test command not being found, dependency installation failing, or zero tests being collected. Only genuine test failures go to reflect and consume budget. Infrastructure errors get a bounded retry of the sandbox itself. If they persist, the run escalates with a clear 'environment problem' label. A wrong test command from the developer should fail fast with a helpful message, not burn iterations.

Sourced from Docs by LangChain (MCP)

20Why does the reflect node increment the iteration counter rather than the generate node?Often asked

The counter measures how many times we decided to retry after a failure. Reflect runs exactly once per retry decision, so incrementing there ties the budget directly to the retry path that route_after_tests guards. If generate incremented it, the first attempt would also count, which shifts the boundary by one and is easy to misread. Generate may also be re-entered after a reviewer hint, and that should not burn budget by accident. Putting the increment in one node gives a single owner for the counter. Tests can then assert the invariant directly: after k reflect visits, iteration equals k, and escalation happens once iteration reaches max_iterations.

Sourced from Docs by LangChain (MCP)

21Test logs can be huge. How do you keep the reflect step useful within the model's context window?Often asked

The sandbox already truncates stdout and stderr, but truncation must be smart. I keep the failing test names, the assertion messages and the top and bottom of each stack trace, because the useful signal is usually there. Long, repetitive output such as dependency warnings gets collapsed. The full raw logs go to object storage with a reference in state, so engineers can read them later without bloating checkpoints. Reflect gets the structured summary plus the relevant code region. If logs are still too large, I rank failures and send the first few distinct root errors. One clear failure usually fixes several others.

Sourced from Docs by LangChain (MCP)

22How would you estimate checkpoint storage for this workflow?Often asked

Storage grows with tasks, super-steps per task and state size per checkpoint. Each loop iteration passes through several super-steps: generate, sandbox, routing into reflect, and reflect. Each checkpoint stores state, including the growing attempts list. A rough formula is tasks per day times average super-steps times average serialized state size. I would measure the real averages from a pilot rather than assume them. To keep it bounded, store only truncated logs in state, keep raw logs in object storage, and set a retention policy that prunes old threads or keeps only final checkpoints after a set period. Lessons in the store are small and grow slowly by comparison.

Sourced from Docs by LangChain (MCP)

23How would you stream progress to the developer while the loop runs?Often asked

LangGraph offers several streaming modes, and I would combine them. The updates mode emits each node's state delta, which maps nicely to UI events like 'attempt 2 generated', 'tests failed: 3 failing' or 'reflecting'. The messages mode streams LLM tokens with node metadata, so the UI can show the critique or code as it is written and label which node produced it. The custom mode lets the sandbox emit its own progress events, such as 'container started'. I would avoid values mode for the UI, because it resends the full state, including large attempts, at every step. Debug mode is for engineers, not end users.

Sourced from Docs by LangChain (MCP)

24Checkpoints hold proprietary source code. How do you handle privacy and retention?Often asked

Every checkpoint contains task text, code and logs, so the checkpoint database is as sensitive as the repo. Encrypt it at rest and in transit, restrict access by role, and tie thread access to the requesting developer or team. Redact secrets and personal data from logs before they enter state. Define a retention policy, for example pruning intermediate checkpoints after a period and keeping only the final result and its evidence. Do this per your organisation's policy. Scope lesson namespaces per repo so code patterns do not leak across tenants. Also check the LLM provider's data handling terms, and use a provider or deployment that meets your compliance needs for source code.

Sourced from Docs by LangChain (MCP)

25Why generate a full candidate solution each time instead of incremental patches?Often asked

A full solution for the target files keeps each attempt self-contained. Any checkpoint can be run, compared or forked on its own, without replaying a chain of patches. It also avoids broken states where patch three assumes patch two applied cleanly. The cost is more output tokens, plus the risk that the model rewrites working code it should have left alone. For small or medium files, full regeneration is usually simpler and more reliable. For large files, I would switch to a structured diff format applied by a tool, and validate it before the sandbox. Either way, the sandbox always runs the complete resulting code, never a partial view.

Sourced from Docs by LangChain (MCP)

26How could you use Send to try several candidates in parallel, and is it worth it?Often asked

A conditional edge can return a list of Send objects, each targeting generate with its own per-item state, such as a different temperature, a different model or a different strategy hint. Each branch runs its own sandbox test, and results flow back through a reducer, for example appending to attempts. A collector then picks any candidate that passed. This is map-reduce. It cuts wall-clock time for hard tasks and adds diversity. The trade-off is cost: you pay for several generations and sandbox runs at once, even when the first would have passed. I would enable it only for tasks that already failed a sequential attempt, and count each parallel branch against the budget.

Sourced from Docs by LangChain (MCP)

27How do you test the graph itself?Often asked

Test in layers. Test route_after_tests as a pure function across every boundary: pass, fail below budget, fail at budget, and malformed results. Test nodes with a fake LLM that returns scripted outputs and a fake sandbox that returns scripted exit codes. Then assert the full path, for example fail, fail, pass reaches finalize with iteration at 2. Use InMemorySaver in tests to check interrupt and resume with Command(resume=...). Add a property test that, for any scripted sequence of failures, the graph always terminates or pauses. Finally, run end-to-end tests against a real sandbox on a small fixture repo, and check that finalize never runs without a recorded passing result.

Sourced from Docs by LangChain (MCP)

28Why have a separate reflect node instead of just feeding the logs back into generate?Often asked

Reflection makes the model commit to a diagnosis before writing code: the root cause and a concrete change. Feeding raw logs straight into generate tends to produce small random edits that fix symptoms. A separate node also gives you a stored critique in state, which is useful for the reviewer summary, for debugging, and for forking with a corrected critique. It also lets you use a different prompt, or even a different model, for diagnosis than for coding. The cost is one extra LLM call per retry. That is usually worth it, because a good critique can save a whole wasted attempt.

Sourced from Docs by LangChain (MCP)

29What happens if two workers try to run the same thread_id at once?Often asked

Both would read the same checkpoint and write conflicting next steps. You could get duplicate sandbox runs, a doubled iteration count, or two resumes of one interrupt. The fix is to have a single active run per thread. A deployment platform like LangSmith's Agent Server manages runs per thread for you. If you build your own, use a per-thread lock or a queue that serialises runs for a thread_id. Resuming an interrupt should also be guarded, so a second reviewer action is rejected once the first is accepted. Different tasks use different thread_ids, so this is about a single thread, not overall concurrency.

Sourced from Docs by LangChain (MCP)

30Finalize writes a lesson to the store and streams the result. How do you keep that consistent if one part fails?Often asked

The checkpoint and the store are separate writes, so they are not atomic together. I would order the work so the critical outcome is recorded first: the passing diff and the test evidence go into state and get checkpointed. Writing the lesson is best-effort. If it fails, the task is still a success, and the failure is logged or retried. Lesson writes should be idempotent, for example keyed by thread_id, so a retried finalize does not create duplicates. Any external action, like opening a pull request, should also use an idempotency key. That way a crash and resume inside finalize does not create two PRs.

Sourced from Docs by LangChain (MCP)

31Would you switch models between attempts, and how?Sometimes asked

Sometimes, yes. A cheaper, faster model handles most first attempts. If it fails repeatedly, moving to a stronger model for later attempts can solve harder tasks without paying the premium on every task. I would store the model choice in state, so generate reads it, and have reflect or the router upgrade it after a given number of failures. Each attempt records which model produced it, which matters for later analysis. Engineers can also do this after the fact: use update_state to fork an old checkpoint with a different model and compare results. The test gate stays the same whichever model is used.

Sourced from Docs by LangChain (MCP)

32How would you expose this workflow to other tools and agents?Sometimes asked

Deploying on LangSmith's Agent Server exposes the graph over an API, so a CLI, IDE plugin or CI job can create a thread, start a run, stream updates and resume interrupts. Agent Server also exposes an MCP endpoint at /mcp, so MCP-capable agents can call the workflow as a tool. Whatever the entry point, the same safety rules apply. Authentication decides who can start and resume threads. Inputs such as the test command are validated against an allow-list rather than run as arbitrary shell. Results always carry the test evidence, so callers can check that a pass is real.

Sourced from Docs by LangChain (MCP)

33How might you evolve this into a larger multi-agent system?Sometimes asked

The loop works well as a subgraph. A parent graph can call it as a node for each file or subtask, possibly fanning out with Send for independent changes, and then run an integration test step at the end. A supervisor pattern could route work to specialist agents, for example one for writing tests, one for implementation and one for security review, each with its own bounded loop. The invariants should hold at every level: every loop has a budget, and nothing reaches a merge without passing real tests. Hierarchy adds coordination cost, so I would only add it when one loop clearly cannot handle the task size.

Sourced from Docs by LangChain (MCP)

Objective questions 33

1Tests fail and iteration equals max_iterations. What does route_after_tests return?Asked almost every time
  1. Areflect
  2. Bfinalize
  3. CescalateCorrect answer
  4. Dgenerate

Why: Retry is allowed only while iteration < max_iterations. Once the budget is spent, a failure routes to escalate.

Sourced from Docs by LangChain (MCP)

2What is the only condition that should send a result to finalize?Asked almost every time
  1. AThe reviewer approves the escalation
  2. BThe LLM says the code is correct
  3. CThe reflect node finds no issues
  4. DThe real test command passed in the sandboxCorrect answer

Why: The workflow must never ship code that did not pass the real tests, so finalize depends only on a real test pass.

Sourced from Docs by LangChain (MCP)

3What is the strongest defence against the model passing by editing the test files?Asked almost every time
  1. ARestore tests from the protected snapshot, or reject diffs that touch test paths, before runningCorrect answer
  2. BRaise max_iterations
  3. CUse a larger model
  4. DTell the model in the prompt not to change tests

Why: The tests must be outside the model's control, enforced in code. Prompt instructions can be ignored or overridden by injection.

Sourced from Docs by LangChain (MCP)

4Which sandbox setting is most important for stopping generated code from leaking secrets?Asked almost every time
  1. AUnlimited CPU
  2. BNetwork restriction and no credentials inside the containerCorrect answer
  3. CReusing one warm container across attempts
  4. DRunning as root for compatibility

Why: With no outbound network and no secrets inside, hostile code has nothing to steal and no channel to send it out.

Sourced from Docs by LangChain (MCP)

5Which state definition lets each generate node call add its attempt to a history list instead of overwriting it?Very often asked
  1. Aattempts passed through Send objects
  2. Battempts: list with no reducer
  3. Cattempts: Annotated[list, operator.add]Correct answer
  4. Dattempts stored only in InMemoryStore

Why: A reducer such as operator.add on a state key tells LangGraph to combine node updates by appending to the list. Without a reducer, each update replaces the value.

Sourced from Docs by LangChain (MCP)

6After the retry budget is exhausted, which LangGraph feature pauses the run so a human can supply a hint and continue the same thread?Very often asked
  1. AStarting a new thread_id with the hint
  2. BReturning END from the router
  3. CRaising an exception inside the node
  4. DCalling interrupt() in a node and resuming with Command(resume=...)Correct answer

Why: interrupt() pauses execution with state saved by the checkpointer, and Command(resume=...) continues from that point with the human's input.

Sourced from Docs by LangChain (MCP)

7Which node increments the iteration counter?Very often asked
  1. AreflectCorrect answer
  2. Bfinalize
  3. CThe sandbox tool
  4. Dgenerate

Why: The reflect node diagnoses the failure, increments the iteration counter and stores the critique.

Sourced from Docs by LangChain (MCP)

8What keeps all attempts for one task together in the checkpointer?Very often asked
  1. AA shared namespace tuple
  2. BA single thread_idCorrect answer
  3. CThe Send object ID
  4. DThe sandbox container ID

Why: Checkpointers save state per thread_id, and the service starts one new thread_id per task.

Sourced from Docs by LangChain (MCP)

9Which checkpointer fits this workflow in production?Very often asked
  1. ANo checkpointer, just logs
  2. BInMemorySaver
  3. CA Postgres checkpointerCorrect answer
  4. DInMemoryStore

Why: InMemorySaver is meant for development. Production durable execution uses a Postgres checkpointer.

Sourced from Docs by LangChain (MCP)

10When does the checkpointer save state?Very often asked
  1. AOnly after the sandbox finishes
  2. BOnly when the graph reaches END
  3. COnly when interrupt() is called
  4. DAt every super-stepCorrect answer

Why: LangGraph checkpointers save state at every super-step, which is what makes resume and time travel possible.

Sourced from Docs by LangChain (MCP)

11Where are cross-task fix lessons stored?Very often asked
  1. AIn PostgresStore under a namespace like (repo, 'fix_lessons')Correct answer
  2. BIn the sandbox container filesystem
  3. CIn state.attempts
  4. DIn the checkpointer under the same thread_id

Why: Stores give long-term memory across threads and are organised by namespace tuples.

Sourced from Docs by LangChain (MCP)

12What type of edge connects reflect back to generate?Very often asked
  1. AAn interrupt edge
  2. BA conditional edge
  3. CA normal edgeCorrect answer
  4. DA Send fan-out

Why: After reflect, a normal edge always sends control back to generate. The branching happens only after the tests.

Sourced from Docs by LangChain (MCP)

13What does the reviewer's Command(resume=...) do in the escalate node?Very often asked
  1. ASupplies a value to the paused interrupt() so the same run continuesCorrect answer
  2. BForces the run to finalize
  3. CStarts a new thread_id
  4. DDeletes all checkpoints

Why: interrupt() pauses the node, and Command(resume=...) delivers the reviewer's value so execution continues on the same thread.

Sourced from Docs by LangChain (MCP)

14Which streaming mode delivers LLM tokens tagged with the node that produced them?Very often asked
  1. Aupdates
  2. BmessagesCorrect answer
  3. Cdebug
  4. Dvalues

Why: The messages mode streams LLM tokens with node metadata.

Sourced from Docs by LangChain (MCP)

15Which method lists past checkpoints so an engineer can inspect any attempt?Very often asked
  1. Aget_state_historyCorrect answer
  2. Binterrupt
  3. CCommand(goto=...)
  4. Dupdate_state

Why: get_state_history returns the thread's checkpoints for inspection and time travel.

Sourced from Docs by LangChain (MCP)

16The test run reports a zero exit code, but no tests were collected. What should happen?Very often asked
  1. AIncrement iteration and finalize
  2. BRoute to finalize
  3. CTreat it as not a valid pass and investigate or escalateCorrect answer
  4. DDelete the test command

Why: A run that executed no tests proves nothing, so it must not count as a real pass.

Sourced from Docs by LangChain (MCP)

17The sandbox container was killed for running out of memory before tests finished. What is the best handling?Very often asked
  1. ARetry without limit until it works
  2. BTreat it as a code failure and reflect
  3. CClassify it as an infrastructure error with a bounded sandbox retry, then escalate if it persistsCorrect answer
  4. DRoute to finalize

Why: Separating infrastructure failures from real test failures avoids rewriting correct code, and keeping the retry bounded preserves the no-unbounded-loop rule.

Sourced from Docs by LangChain (MCP)

18An engineer wants to rerun attempt 2 of a past task with a modified critique and compare results. What should they use?Often asked
  1. Aget_state_history to find the checkpoint, then update_state to fork from itCorrect answer
  2. BRaise the recursion_limit and rerun from START
  3. CCopy the code into a new graph without a checkpointer
  4. DUse the messages streaming mode

Why: Time travel in LangGraph works by locating an earlier checkpoint with get_state_history and forking from it with update_state, which creates a new branch without changing the original history.

Sourced from Docs by LangChain (MCP)

19Iteration starts at 0, max_iterations is 3 and every attempt fails. How many generate calls run before escalation?Often asked
  1. A5
  2. B2
  3. C3
  4. D4Correct answer

Why: After failures with iteration at 0, 1 and 2, the run reflects, ending each time at 3 or below. The fourth failure sees 3 < 3 as false and escalates, so generate runs 4 times.

Sourced from Docs by LangChain (MCP)

20How is data in a LangGraph Store organised?Often asked
  1. ABy node name
  2. BBy thread_id only
  3. CBy namespace tuplesCorrect answer
  4. DBy super-step number

Why: Stores such as PostgresStore organise items by namespace tuples, with optional semantic search.

Sourced from Docs by LangChain (MCP)

21A node returns a new attempts list, but the key has no reducer. What happens?Often asked
  1. AThe new value is appended
  2. BThe new value overwrites the previous listCorrect answer
  3. CLangGraph raises an error
  4. DThe value is ignored

Why: Without a reducer, a state key is overwritten. operator.add is what makes updates append.

Sourced from Docs by LangChain (MCP)

22What does interrupt() need in order to pause and later resume a run?Often asked
  1. AThe messages streaming mode
  2. BA checkpointerCorrect answer
  3. CA PostgresStore
  4. DA Send object

Why: Pausing and resuming depends on the run's state being persisted, which is the checkpointer's job.

Sourced from Docs by LangChain (MCP)

23Which streaming mode is best for lightweight per-node progress events without resending the full state?Often asked
  1. AupdatesCorrect answer
  2. Bmessages
  3. Ccustom only
  4. Dvalues

Why: updates emits each node's state changes, while values sends the whole state every time.

Sourced from Docs by LangChain (MCP)

24Which mode would the sandbox use to emit its own events, such as 'container started'?Often asked
  1. Amessages
  2. Bupdates
  3. CcustomCorrect answer
  4. Dvalues

Why: The custom streaming mode carries user-defined events emitted from inside nodes or tools.

Sourced from Docs by LangChain (MCP)

25You want to generate three candidates in parallel and test each one. Which LangGraph feature fits?Often asked
  1. Aget_state_history
  2. BInMemoryStore
  3. Cinterrupt()
  4. DReturning Send objects from a conditional edgeCorrect answer

Why: Send objects returned from a conditional edge fan out to a node with per-item state, which is the map-reduce pattern.

Sourced from Docs by LangChain (MCP)

26How do you fork from an earlier attempt using a different model?Often asked
  1. ARaise max_iterations in the code and redeploy
  2. BEdit the Postgres rows directly
  3. CCall update_state on that checkpoint with the new settings, then continue from itCorrect answer
  4. DStart a new thread and copy the logs by hand

Why: update_state on an earlier checkpoint creates a fork that the graph can continue from.

Sourced from Docs by LangChain (MCP)

27What does Command(goto=..., update=...) allow inside a node?Often asked
  1. ARouting to another node and updating state from inside the nodeCorrect answer
  2. BCreating a new Store namespace
  3. CRestarting the sandbox
  4. DStreaming tokens

Why: Command with goto and update routes and updates state from within a node, for example in handoffs.

Sourced from Docs by LangChain (MCP)

28Why does the sandbox use a fresh container for every attempt?Often asked
  1. AIt prevents state or tampering from one attempt affecting the nextCorrect answer
  2. BLangGraph requires it
  3. CIt reduces token usage
  4. DIt is faster than reuse

Why: A fresh container gives clean, reproducible results, and an earlier candidate cannot leave files or processes behind.

Sourced from Docs by LangChain (MCP)

29A reviewer resumes an escalated run with a hint. How should the loop stay bounded?Often asked
  1. ARemove the conditional edge
  2. BSkip the tests for the next attempt
  3. CSet max_iterations to infinity
  4. DGrant a fixed, capped number of extra iterationsCorrect answer

Why: Granting a limited extra budget gives the loop a new finite limit, so it still cannot run forever.

Sourced from Docs by LangChain (MCP)

30What is InMemorySaver appropriate for?Often asked
  1. ADevelopment and testsCorrect answer
  2. BLong-term memory across threads
  3. CStoring sandbox logs
  4. DProduction durable execution across restarts

Why: InMemorySaver loses state when the process ends, so it suits development, while Postgres is used in production.

Sourced from Docs by LangChain (MCP)

31Two workers resume the same thread_id at the same time. What is the main risk and fix?Often asked
  1. ASlower streaming, fixed by using values mode
  2. BNo risk, since checkpoints merge automatically
  3. CConflicting writes such as double counting, fixed by ensuring a single active run per threadCorrect answer
  4. DLost Store data, fixed by adding namespaces

Why: Concurrent runs on one thread read the same checkpoint and write conflicting steps, so runs per thread must be serialised.

Sourced from Docs by LangChain (MCP)

32On LangSmith's Agent Server, at which path is the MCP endpoint exposed?Sometimes asked
  1. A/v1/mcp-server
  2. B/agent
  3. C/mcpCorrect answer
  4. D/tools

Why: Agent Server exposes deployed agents over an API and an MCP endpoint at /mcp.

Sourced from Docs by LangChain (MCP)

33You want to reuse the whole generate-test-reflect loop as one step inside a larger graph. What fits?Sometimes asked
  1. AA subgraph used as a nodeCorrect answer
  2. BA Store namespace
  3. CThe debug streaming mode
  4. DInMemorySaver

Why: Subgraphs compose graphs as nodes, so the loop can be embedded in a parent workflow.

Sourced from Docs by LangChain (MCP)

Track your progress through every blueprint in your dashboard.

Start learning