Free learning / Agent engineering lab
Build and test a bounded AI agent
Run real LangChain tool loops and LangGraph SQLite restart labs, compare broken candidates and inspect source-bound review without a provider key.
No account needed. Use your own copy for writing and keep the original as a reference.
Engineering lab library / Run locally
Choose your next agent lab.
Start with the durable workflow reference, then choose the boundary you want to test. Open its walkthrough for setup, commands and expected observations.
Synthetic records. No provider key or model inference. Framework setup needs package-registry access.
Search titles, outcomes and prerequisites, then narrow by runtime.
13 of 13 labs · All labs
Start here / Python 3.11+ · Standard library
Durable workflow reference
Pause a draft, restart the process and bind a review decision to its exact source.
One Python file · No package installation
Python 3.11+ · Standard library
Scoped retrieval and context
Compare retrieval quality on eight cases while preserving current access and context budgets.
Python standard library · Standalone file
Python 3.11+ · Standard library
Regression comparison lab
Compare the working reference with broken candidates and retain missing-case evidence.
Python standard library · Keep the workflow file beside it
Node.js 24 · Offline exercise
MCP tool admission
Challenge changed tool definitions, forged arguments and current host permissions.
Node.js 24 · Offline host-policy exercise
Node.js 24 · Offline exercise
Shared delegation coordinator
Trace retries, shared attempt limits and late results after manual closure.
Node.js 24 · Supplied event replay
Python 3.11+ · Standard library
Worker lease and recovery
Compete for one lease across two local processes, then rehearse a worker kill and recover without resetting attempts.
Python standard library · SIGKILL extension on macOS/Linux
Real framework · Scripted inputs
LangGraph SQLite restart
Recover sequential and parallel failures from saved work, then distinguish retry from human review.
Python 3.11+ · New virtual environment · Three pinned packages
Real framework · Scripted inputs
LangGraph interrupt and resume
Wire a real StateGraph, observe an interrupt and resume its in-memory state.
Python 3.11+ · New virtual environment · Pinned LangGraph package
Real framework · Scripted inputs
LangChain tool-calling loop
Inspect real create_agent orchestration using scripted tool proposals and results.
Python 3.11+ · New virtual environment · Pinned framework packages
Python 3.11+ · Standard library
Receiver commit and replay
Compare a duplicate-producing receiver with an atomic effect and receipt, then retry a lost response.
Python standard library · Owned child-process exits
Python 3.11+ · Standard library
Agent trace and payload boundary
Keep useful observability fields, separate model claims from application observations and refuse payload-bearing trace events.
Python 3.11+ · Standard library · Standalone file
Node.js 24 · Offline exercise
MCP stdio compatibility
Inspect real framed requests and distinguish a tool refusal from a protocol failure.
Node.js 24 · Owned child process · Pinned 2025-11-25 profile
Python 3.11+ · Standard library
Agent canary evidence
Compare candidate/control segments, retain missing evidence and stop an aggregate-green regression.
Python 3.11+ · Standard library · Supplied observation packets
Original LockedIn Labs training. All people, tenants and records are synthetic. These files execute on your machine. No API key, customer data, model inference or external sending is used by any supplied lab.
Use the downloads on the free lab page, or the Academy course's Practice section. Use a disposable working directory. Python labs require Python 3.11 or later; the delegation coordinator and MCP labs require Node.js 24. Never put real source documents or credentials in these examples.
0. Choose a lab and prepare your workspace
This is an original hands-on route for forward-deployed engineers who can run a terminal command and read a small Python function. If you are new to coding, first trace the supplied inputs, state changes and refusal decisions on paper; label that work unexecuted. Python labs need Python 3.11 or later and a text editor. Use python3 --version to check your interpreter before starting.
Use the lab library on the free page to filter by runtime and open the exact walkthrough for your chosen file. Download the files you need and save them in a new disposable folder. The evaluation lab imports the workflow file from that same folder, so keep both original filenames. You do not need an account, API key or package installation for the standard-library labs.
| Start with | Preparation | Evidence to keep |
|---|---|---|
| Durable workflow reference | Python standard library; one .py file | A paused draft, a fresh-process read and a bound decision. |
| Scoped retrieval | Python standard library; standalone file | Authorized evidence, content-edit invalidation and revoked access. |
| Regression comparison | Workflow and evaluation .py files together | Reference, broken-candidate and missing-case reports. |
| MCP tool admission | Node.js 24; standalone .mjs file | Definition change, current grant and argument/result boundary decisions. |
| MCP stdio compatibility | Node.js 24; standalone file; owned child process | Correlated wire frames, a useful tool result and protocol/tool refusals. |
| Agent canary evidence | Python standard library; standalone file | Segment comparisons, incomplete evidence and a non-authorizing review recommendation. |
| Shared delegation | Node.js 24; standalone .mjs file | Consumed attempts, freed slots and late-result refusal. |
| Worker recovery | Python standard library; actual SIGKILL extension on macOS/Linux | A committed lease after process death, bounded reclaim and current receipt. |
| LangGraph SQLite restart | Separate environment with three pinned packages | The same real graph checkpoint inspected and resumed in fresh processes. |
| LangGraph wiring | Separate virtual environment and pinned package | An observed interrupt and scripted resume; in-memory only. |
| LangChain tool loop | Separate virtual environment and pinned packages | Proposed tool, dispatched result and stop reason. |
Choose one lab rather than running every command at once. The sections below contain the commands in order. Start with the standard-library workflow, inspect one refusal, then compare the regression candidates. Framework installation requires normal package-registry access; installed examples run without provider inference. They use real framework orchestration with scripted proposals.
If Python reports that a file is missing, check your working directory and the original filename. If the evaluator cannot import its reference, put both files beside each other. A deliberately blocked candidate exits 2; preserve its JSON instead of treating that exit as a package failure. Never fix a refusal by removing the relevant case or using a real customer's records.
1. Run the durable workflow reference
The reference uses only the Python standard library. Run:
python3 fde-agent-engineering-lab.py test
python3 fde-agent-engineering-lab.py demo --db field-desk.sqlite
python3 fde-agent-engineering-lab.py inspect --db field-desk.sqlite
python3 fde-agent-engineering-lab.py review --db field-desk.sqlite --decision accepted
python3 fde-agent-engineering-lab.py inspect --db field-desk.sqliteThe test command exits nonzero on failure. Retain your actual output rather than copying an expected transcript. A successful run exercises nine cases: useful authorized work, foreign source denial, foreign run denial, restart and request replay, conflicting request reuse, decision replay/conflict, changed proposal, changed source and withdrawn source.
demo creates an awaiting_review record. Every following command is a fresh process reading the same SQLite file. review records only acceptance or rejection of that synthetic draft. It does not send a response. Repeat the same accepted decision and verify it is stable. Attempt --decision rejected after acceptance and inspect the conflict. For a foreign-tenant read, use:
python3 fde-agent-engineering-lab.py inspect --db field-desk.sqlite --tenant southThat command must refuse. This CLI is not an authentication service: a deployed host must derive tenant identity from an authenticated session and authorize reviewers before invoking the domain methods. Its SQLite transaction is a local reference, not evidence about a distributed fleet.
1c. Recover an abandoned worker lease
Download fde-worker-recovery-lab.py from the free lab page. It is a standalone standard-library exercise using only temporary synthetic SQLite records. Predict whether the abandoned worker may record a completion before running:
python3 fde-worker-recovery-lab.py test
python3 fde-worker-recovery-lab.py demoThe demo records a queued admission, worker A’s lease, a database-connection restart and worker B’s reclaim at lease expiry. B receives fencing number 2; A’s attempt to finish with number 1 is rejected as lease_lost. B records the current synthetic result. The timestamps are injected clock ticks, not observed latency or a process-kill experiment. The admission is not a completion, and a finished worker record is not a human acceptance or an external-effect receipt.
Inspect the original eight queue tests as well as your actual demo JSON. They cover bound request replay, per-tenant capacity, lease expiry, stale completion, cancellation, checkpoint-version compatibility, persisted attempt limits and stable result receipts. An unchanged request replay cannot resurrect a cancelled job; an old worker cannot claim a declared newer checkpoint format. The format marker is a simple gate, not state migration or complete deserialization evidence.
Write the operator’s next action for each record:
| Observation | Next safe decision to explain |
|---|---|
| A worker has a live lease | Keep a second worker from stealing it. |
| A lease expires and another worker reclaims it | Preserve the increasing fencing number; reject stale completion. |
| Cancellation invalidates the lease | Stop local acceptance; investigate any effect already in flight. |
| The attempt budget is exhausted | Retain the record and investigate; do not silently replenish attempts. |
| Rolled-back code cannot read the format marker | Keep work unclaimed until compatible handling is established. |
The last stored running state with an expired lease is not evidence that a business action failed. This lab has no external effects. Its fencing check protects only its completion row; an external destination needs idempotency, supported fencing or reconciliation of uncertain outcomes. Cancellation does not kill the old process or undo an already-issued request. Do not infer at-most-once business execution from a local completion refusal.
The synthetic tenant and worker arguments stand in for trusted host context. Before exposing these methods through a service, authenticate and authorize callers, derive tenant scope and supply an authoritative clock. Fencing numbers are concurrency versions, not credentials. The local writer transaction is not a distributed queue, fleet-concurrency test, deadline controller or a deployed checkpoint backend. Keep those limitations in the receiving-team runbook.
Run a real local process-kill rehearsal
On macOS or Linux, use the same standalone file with Python 3.11 or later:
python3 fde-worker-recovery-lab.py crash-demoThis command creates a temporary synthetic database, starts its own child worker and waits for a flushed lease_committed event. That worker has committed the lease and is idle with its SQLite connection open. The parent kills only that owned child with SIGKILL, waits for its exit and reopens the database. It does not accept a process ID or connect to your application database. The internal worker entry refuses an existing file. Temporary records are removed after the report is assembled; keep your JSON output separately if you need it later.
Predict each field before running. Compare your actual report with this worksheet:
| Field | Observation to explain |
|---|---|
boundary.event, worker_exit | lease_committed, then -9 on the supported POSIX run. |
boundary.job, after_kill | Identical stored lease; still running, with a null result digest. |
integrity_check | SQLite returns ok for this local after-commit experiment. |
before_expiry, attempts_before_reclaim | lease_busy at injected tick 4; one attempt remains consumed. |
reclaimed | Worker B holds fence 2 at injected tick 5; two attempts are consumed. |
reconstructed_stale_submission | A synthetic replay of A's old lease is refused as lease_lost. |
finished, receipt_replay_equal | B records the synthetic result; reading the same receipt is stable. |
The stale submission is reconstructed by the parent. It is not a message sent by the killed worker. The clock ticks are injected, so this is not a measurement of expiry delay or recovery time. The ten bundled tests include the real process-boundary check and preservation of an existing file; those two process tests explicitly skip on unsupported systems. On Windows use demo and record this SIGKILL rehearsal as unrun. The command uses no model, network, customer records, sending action or authenticated service.
Keep the distinction between a process killed after commit while idle and a kill inside a transaction, a host power failure, a storage outage or a remote queue failover. This exercise does not establish those properties or prove at-most-once external effects. Python documents the POSIX kill and return-code behavior; SQLite describes its commit boundaries and storage assumptions.
For a receiving task, ask another engineer to identify the observation that permits reclaim, explain why the failed pre-expiry attempt spends no budget and propose one still-unrun failure experiment. Keep the original JSON with that operator note; do not relabel a proposed experiment as executed evidence.
Compete for one job from separate processes
Use Python 3.11 or later and the same standalone worker file. Predict the consumed attempt count before running:
python3 fde-worker-recovery-lab.py race-demoThe command admits one synthetic R1 job in its own temporary database and starts two fresh Python processes. Each opens a separate connection before a shared starting gate releases both. Either worker may win; the gate does not guarantee overlapping execution or a particular ordering. Inspect your actual report instead of assigning worker A the winning role in advance.
| Report field | Observation to explain |
|---|---|
workers | Two distinct process IDs; one claimed, one lease_busy. |
Winning fence | 1: only one claim was admitted. |
after_claims.attempts | 1, not 2: the refused contender did not consume another attempt. |
after_claims.state | running; result_digest is still null. A lease is not completion. |
integrity_check | ok on the local file after both processes exit. |
The current claim method begins a writer transaction before reading the job, checking its lease and updating its ownership. A process-local lock would not coordinate these separate workers. The second writer must inspect the committed lease and refuse while it is live. SQLite's bounded lock wait can instead fail with a database error under other contention; that is different from the domain's lease_busy refusal. Do not translate a storage failure into permission to run. SQLite transaction control explains the single-writer boundary and BEGIN IMMEDIATE behavior.
Receiving task: give a peer the JSON without your explanation. Have them identify the current owner, consumed attempts and evidence still missing before calling the job complete. Then inspect the sequential expiry demo: explain why reclaim at injected tick 5 may increment the fence, while a competing claim at tick 0 may not. Keep this local observation separate from fleet capacity, clock agreement, lock-timeout recovery, network partitions and external effects. No throughput, distributed execution or authenticated worker identity is tested.
1e. Recover a receiver after its response is lost
25-minute local lab · Python 3.11+ · No packages or provider key
An agent worker can lose its connection after a destination commits an action. Restarting that worker does not establish whether the action happened. Download fde-receiver-recovery-lab.py from the lab chooser. Before running it, predict the effect and receipt counts for each boundary below.
python3 fde-receiver-recovery-lab.py test
python3 fde-receiver-recovery-lab.py demoThe parent starts only children of this lab in a temporary synthetic workspace. Each child exits abruptly at an explicitly named SQL boundary. Exit 24 occurs after both inserts but before commit; exit 23 occurs after commit but before printing a response. Exit 25 belongs to a deliberately broken receiver that commits its effect separately from its receipt. These codes are lab markers, not HTTP status codes. The temporary files are removed after the report.
| Boundary | Before retry: effects / receipts | After retry: effects / receipts |
|---|---|---|
| Before shared commit | 0 / 0 | 1 / 1 |
| After shared commit, response missing | 1 / 1 | 1 / 1 |
| Broken: effect committed alone | 1 / 0 | 2 / 1 |
These are expected local observations; keep the actual JSON from your own run. A missing response means the caller has no receipt. It does not mean the destination has no effect. Inspect before_retry before reading after_retry. For the shared transaction, BEGIN IMMEDIATE serializes the local writer check; the unique tenant/request key binds a canonical payload digest to an effect ID. The effect and receipt commit together. Retrying the same key and payload returns the stored ID. A different payload conflicts; a new key can create another effect. This is request replay protection, not business-level duplicate detection.
Inspect test_distinct_processes_racing_same_key_share_one_effect: two actual processes target one local database and receive the same receipt. Replays still check the current synthetic work-order grant. A key, digest or tenant name is not an authentication credential. In a deployed host, derive tenant context from verified identity and authorize the specific action; do not trust an agent's argument to supply either authority or scope.
Receiving task. Explain the broken receiver's 1 / 0 state to another engineer, then show the retry that produces 2 / 1. Ask them to identify the transaction boundary that repairs it. Propose one changed-payload experiment, run it, retain the conflict and unchanged effect count, and record their actual challenge and your revision. Keep a separate unrun register for remote service failure, database failover, retention expiry and a crash inside commit.
The row in effects is the entire synthetic effect. No email, payment, real tool call, model or network request runs. This is abrupt local process exit around commit, not a power-loss test or a distributed exactly-once guarantee. A local transaction cannot atomically include an unrelated remote service. That service needs its own supported idempotency contract, stable operation identity and an authorized outcome lookup or reconciliation path. Deleting receipts or choosing a fresh key to clear an uncertain outcome can duplicate work. Set retention and reconciliation policy before adapting the pattern to a real receiver.
Implementation references: Python's explicit transaction control and SQLite's atomic commit boundary. The lab uses explicit SQL with isolation_level=None; it does not depend on the newer Python autocommit connection argument.
2. Run a real LangGraph interrupt/resume graph
Keep the framework experiment separate from the standard-library reference. In a new isolated virtual environment, install LangGraph from its normal Python package registry, then record the actual resolved versions:
python3 -m venv .venv
. .venv/bin/activate
python -m pip install "langgraph==1.2.16"
python -m pip freeze > requirements.lock.txt
python fde-langgraph-lab.pyOn Windows activate with .venv\Scripts\activate. Keep the lock file with your practice evidence; use it to reproduce the same environment. The top-level package is pinned to the version used for this walkthrough; transitive versions are captured by your freeze output. It is not a complete pre-resolved lock manifest. The supplied graph was run with Python 3.12 and LangGraph 1.2.16 on 17 October 2026. Follow the linked upstream documentation if your installed version differs.
The graph uses StateGraph, InMemorySaver, interrupt and Command. It asserts that the first invocation waits for review, then scripts an accepted resume. This checks graph wiring with a deterministic drafting node. It is neither a live model evaluation nor a real human-review session. InMemorySaver does not survive a process ending. The SQLite reference illustrates a separate durable design; it is not presented as a LangGraph checkpoint adapter.
2a. Resume a real LangGraph after process exit
Download fde-langgraph-durable-lab.py. This uses the real LangGraph StateGraph, interrupt and SqliteSaver APIs. It is separate from both the earlier in-memory walkthrough and the standard-library SQLite reference. Use a new disposable working folder and isolated environment:
python3 -m venv .venv-graph
.venv-graph/bin/python -m pip install "langgraph==1.2.16" "langgraph-checkpoint==4.3.0" "langgraph-checkpoint-sqlite==3.1.2"
.venv-graph/bin/python -m pip freeze > langgraph-requirements.lock.txt
.venv-graph/bin/python fde-langgraph-durable-lab.py test
.venv-graph/bin/python fde-langgraph-durable-lab.py demo --db graph-demo.sqliteOn Windows replace .venv-graph/bin/python with .venv-graph\Scripts\python.exe. Package installation needs normal registry access; installed lab commands need no model key and refuse socket connections. The three package versions above were used with Python 3.12.14 on 18 October 2026. Retain your own resolved lock output and observations.
Fourteen tests check real process-separated persistence, scope naming, refused resume, sequential terminal replay/conflict, rejection, per-request proposal binding and preservation of an existing file. The scripted demo uses four child processes: start, inspect, accept and repeat the decision. Compare these fields:
| Observation | What it establishes locally |
|---|---|
| First process: awaiting_review, next review, interrupted true | A pending graph checkpoint exists. |
| Second process: same digest and pending state | A fresh graph instance reads the saved work. |
| Third process: accepted, empty next, interrupted false | The scripted bound decision resumed this local graph. |
| Fourth process: decision-replayed | The sequential host guard returns the recorded decision. |
The demo requires a new filename. An existing graph-demo.sqlite is preserved with demo-needs-new-file; inspect it or choose another disposable filename. Do not reuse a real application database. For manual work, run start and inspect against another new local file:
.venv-graph/bin/python fde-langgraph-durable-lab.py start --db graph-practice.sqlite
.venv-graph/bin/python fde-langgraph-durable-lab.py inspect --db graph-practice.sqliteCopy the digest from your actual inspect JSON. Replace the literal PASTE_OBSERVED_DIGEST below with that value; it is a proposal fingerprint, not a credential. Leaving the placeholder in place must refuse the resume.
.venv-graph/bin/python fde-langgraph-durable-lab.py resume --db graph-practice.sqlite --decision accepted --digest PASTE_OBSERVED_DIGESTBefore acceptance, try a wrong digest, add --source-version 4, or add --deny-review. Each must return a named refusal with exit 2 and preserve the pending checkpoint. The flags simulate trusted host source/reviewer state; they are not authentication. Start a second request with --request R2 and show that the R1 digest cannot approve it. Then accept the correct proposal, repeat the decision and try the conflicting rejected decision. Retain the actual outputs rather than counting every process exit as completed work.
The LangGraph interrupt guide explains that the interrupted node restarts from its beginning. This lab puts no external effect before interrupt. Its code enables strict checkpoint MessagePack handling and disables inherited remote tracing. Keep the SQLite file private and never load an arbitrary checkpoint database.
Normal process exit is not a hard-kill, power-loss, concurrency or migration experiment. The sequential host checks are not a distributed compare-and-set; thread naming is not tenant authentication. File access, reviewer authorization, retention, encryption, backup and schema compatibility require separate deployed controls. No message is sent, and the CLI's scripted accepted value is not an observed independent human review or a business outcome.
Recover a failed node before resuming review
20-minute extension · Same pinned environment and file, Edition 2 or later
A node failure is not a human-review interrupt. Run the new scripted failure rehearsal against another fresh disposable database:
.venv-graph/bin/python fde-langgraph-durable-lab.py failure-demo --db graph-failure.sqliteFive separate processes start with a deliberate review-node exception, inspect the checkpoint, retry the failed node, supply a bound decision and replay it. The exception simulates an unavailable dependency; no external outage occurs. The graph, exception propagation, checkpoint storage and process boundaries are real local execution. Compare the actual fields before explaining the result:
| Process | Node calls in this process | Pending failure | Human interrupt |
|---|---|---|---|
| Deliberate failure | draft, review | true | false |
| Fresh inspection | none | true | false |
| Retry without a new input | review | false | true |
| Bound accepted resume | review | false | false |
| Same decision replay | none | false | false |
The failure occurs after the completed draft step has checkpointed. Retrying uses graph.invoke(None, config) on the same thread, not a new input or a human decision. The observed draft digest must remain unchanged. In these pinned versions, task_error_nodes still contains review when retry reaches interrupt. That retained metadata is not itself an instruction to retry again. Inspect interrupted, next and pending_failure together. The latter is a host interpretation for this sequential teaching graph, not a general SDK API. Even the stored application string awaiting_review precedes the actual interrupt, so it cannot by itself distinguish these states.
Try the sequence manually in a new file. The first command deliberately exits 2; this is an observed failure, not a passing execution. Copy the digest from your actual inspect output into each placeholder below (a fingerprint, not a secret):
.venv-graph/bin/python fde-langgraph-durable-lab.py start --db graph-retry.sqlite --fail-before-review
.venv-graph/bin/python fde-langgraph-durable-lab.py inspect --db graph-retry.sqlite
.venv-graph/bin/python fde-langgraph-durable-lab.py retry --db graph-retry.sqlite --digest PASTE_OBSERVED_DIGEST
.venv-graph/bin/python fde-langgraph-durable-lab.py resume --db graph-retry.sqlite --decision accepted --digest PASTE_OBSERVED_DIGESTBefore retry, try resume: it must return retry-required. Try a wrong digest, --source-version 4 or --deny-review with retry; each refuses without running a node. After retry reaches an interrupt, retry must return not-failed and leave it waiting. Retry never supplies a human decision. Review-node entry appears again when the bound decision resumes because code before interrupt re-enters; keep irreversible effects out of that region. No provider or sending tool runs.
Receiving task. Have another engineer explain why the error metadata after retry does not justify another automatic attempt. Retain all five observations, the unchanged digest and per-process node calls. Ask them to distinguish a completed earlier graph step from side effects inside a failed node. A node's own partial external work may repeat on retry; this exercise does not make that work atomic or establish a distributed exactly-once guarantee. Keep automatic retry policy, real dependency outages, concurrency, cancellation and crash recovery inside a node separately unrun. Consult the current checkpointer guide for step/task persistence; retain the actual package versions beside this result.
Recover a parallel step without repeating its completed branch
20-minute extension · Edition 3 · Same pinned packages
The sequential failure exercise finishes the draft in an earlier graph step. This extension schedules draft and policy as sibling branches in the same step and joins them before review. Policy deliberately raises; draft completes. No real policy service, model or external action runs. Predict whether the completed draft needs to run again, then use another new disposable file:
.venv-graph/bin/python fde-langgraph-durable-lab.py parallel-demo --db graph-parallel.sqliteFour fresh processes fail the policy branch, inspect saved work, retry and supply a bound review decision. The local SDK schedules the sibling branches; this exercise does not measure wall-clock overlap, throughput or cost. First-call ordering can vary. Retain the actual JSON and compare these observations:
| Process | Node calls in this process | What to inspect |
|---|---|---|
| Scripted branch failure | draft + policy, order unspecified | task_results.draft true; policy error; no review interrupt |
| Fresh inspection | none | Same draft digest and saved draft task result |
| Retry same thread with no new input | policy, review | Draft does not repeat; policy checked; review interrupted |
| Bound accepted resume | review | Accepted state and no next node |
get_state exposes the saved draft output while the sibling policy task still has an error. Visible draft text, digest or awaiting_review status does not establish that the joined step is complete. Inspect task_results, task_error_nodes, policy_status, next and interrupted together. The checked policy value is a deterministic teaching marker, not an authorization, safety or compliance assessment. The review node will not proceed until that branch returns its marker, and interrupt still requires a separate decision.
For a manual rehearsal, use a different fresh filename. The first command intentionally exits 2. Copy your actual inspect digest into the placeholders:
.venv-graph/bin/python fde-langgraph-durable-lab.py parallel-fail --db graph-parallel-practice.sqlite
.venv-graph/bin/python fde-langgraph-durable-lab.py parallel-inspect --db graph-parallel-practice.sqlite
.venv-graph/bin/python fde-langgraph-durable-lab.py parallel-retry --db graph-parallel-practice.sqlite --digest PASTE_OBSERVED_DIGEST
.venv-graph/bin/python fde-langgraph-durable-lab.py parallel-resume --db graph-parallel-practice.sqlite --decision accepted --digest PASTE_OBSERVED_DIGESTBefore retry, attempt parallel-resume; it refuses with not-awaiting-review. Try a wrong digest, --source-version 4 or --deny-review on parallel-retry; each refuses without discarding the completed draft. After retry reaches interrupt, a second parallel-retry returns not-failed. Replaying the same bound decision returns the recorded result; a conflicting later decision is refused. The parallel graph uses a distinct synthetic thread name from the sequential graph. That separates this exercise's state, not permissions; trusted host identity and access to the checkpoint file still need a real deployment boundary.
Receiving task. Ask another engineer to identify the saved result before retry, then show that the fresh process calls policy and review without draft. Have them explain why starting a new thread or providing a new input would be a different operation, and why completing both branches still does not grant human acceptance. Keep their actual challenge and your revision with the JSON. The SDK persists completed task writes for recovery; it does not undo or make an external effect inside a failed branch atomic. Do not clear the checkpoint to hide a failure. Multi-host coordination, real policy service failures, cancellation, reducer conflicts, schema upgrades, power loss and external exactly-once behavior remain separately unrun. The checkpointer guide explains pending task writes; the fields above are observations of this pinned local experiment.
2b. Run the actual LangChain tool-calling loop offline
Download fde-langchain-agent-lab.py from the free lab page or the LangChain lesson's Practice section. In a new disposable folder, create a separate virtual environment before installing the pinned packages. Record all resolved versions; this walkthrough does not require the earlier LangGraph setup:
python3 -m venv .venv-chain
. .venv-chain/bin/activate
python -m pip install "langchain==1.4.6" "langgraph==1.2.16"
python -m pip freeze > requirements.lock.txt
python fde-langchain-agent-lab.py test
python fde-langchain-agent-lab.py supported
python fde-langchain-agent-lab.py foreign-source
python fde-langchain-agent-lab.py authority-override
python fde-langchain-agent-lab.py unknown-tool
python fde-langchain-agent-lab.py spinOn Windows activate with .venv-chain\Scripts\activate instead.
Five tests passed with Python 3.12 and those top-level package versions on 17 October 2026. Retain your own actual result. The tests also refuse socket connections, and the script disables inherited remote tracing. Package installation needs registry access; the installed lab needs no provider key.
This uses the real LangChain create_agent orchestration and a deterministic BaseChatModel adapter that proposes each supplied tool call. It is not an inference run or a measure of model quality. The ordinary trajectory should be input, tool-proposal, tool-result, answer. The scoped business lookup admits P1 and withholds the foreign P9 text. The strict tool schema rejects an added tenant field before lookup; an ungranted sender is never dispatched.
spin intentionally reaches the graph step limit, reports incomplete and exits with status 2. This is an expected refusal, not an installation failure. A finished graph can still produce a withheld answer. Do not turn either state into an accepted business outcome. The step ceiling is not an elapsed-time, token, distributed-work or external-service budget.
Inspect the source and change a supplied proposal. Add a test that keeps the ordinary request useful while proving the new refusal. A future provider-backed adapter requires a separate environment configuration, model-quality evaluation and execution evidence; do not relabel these scripted results as model results.
2c. Evaluate useful work, stale approval and missing evidence
Download fde-agent-evaluation-lab.py and fde-agent-engineering-lab.py into the same disposable directory. This exercise uses the Python standard library; no package installation, model account or network connection is needed. Use Python 3.11 or later, and record python3 --version with your output.
Predict the failed case IDs before running:
python3 fde-agent-evaluation-lab.py test
python3 fde-agent-evaluation-lab.py run --candidate reference
python3 fde-agent-evaluation-lab.py run --candidate refuse-all
python3 fde-agent-evaluation-lab.py run --candidate stale-review
python3 fde-agent-evaluation-lab.py run --candidate reference --omit-case withdrawn-sourceThe first command verifies the evaluator, not a learner's competence. Each run calls the reference's real domain methods against new temporary synthetic SQLite databases. The two intentionally broken subclasses are teaching copies: refuse-all refuses useful work; stale-review skips the current-source check before acceptance. They are never proposed production changes.
Inspect the actual report rather than copying these expectations:
| Candidate | What the declared contract should expose |
|---|---|
| reference | Utility 1/1, boundary 4/4, recovery 1/1; all six cases observed. |
| refuse-all | Foreign-source denial passes, but useful drafting fails; setup-dependent cases report failed observations. |
| stale-review | Useful drafting and replay pass; changed and withdrawn source acceptance fail. |
| reference with omission | Five executed of six planned; withdrawn-source remains unrun, so the local decision is blocked. |
Exit 0 means local_contract_met. The mutants and omission example deliberately exit 2 with blocked; this is the exercise's expected result, not an installer error. The JSON keeps utility, boundary and recovery denominators separate and lists each failed or unrun case. Duplicate and unknown case IDs are rejected. Unexpected exceptions become failed observations with their class name only; source bodies, database paths and exception messages are not exported.
Change one real requirement before changing code. For example, the current contract does not authenticate reviewers or test revocation between pause and resume. Define that adapter and its acceptance cases rather than treating the existing six passes as evidence for it. Add a new case without removing a failed case from the denominator. Keep predictions, observed JSON, source revision and unrun work in the handover. These are deterministic local observations, not inference-quality scores, customer outcomes or permission to release a service.
2d. Check MCP tool admission
This standalone lab practices the host policy around a Model Context Protocol (MCP) tool. It uses MCP-shaped descriptor fixtures, not a running MCP server or SDK. No packages, network, credentials or model account are needed. Download fde-mcp-contract-lab.mjs, check Node.js 24, then run:
node --version
node fde-mcp-contract-lab.mjs test
node fde-mcp-contract-lab.mjs supported
node fde-mcp-contract-lab.mjs changed-description
node fde-mcp-contract-lab.mjs forged-scope
node fde-mcp-contract-lab.mjs hint-is-not-authority
node fde-mcp-contract-lab.mjs demoThe test command runs nine checks and exits nonzero on a failed assertion. demo prints 15 scenario records. Scenario commands exit 0 when a replay runs, including a refused call; inspect code and dispatches, not just the exit status. Unknown selectors and extra arguments exit 2.
The MCP tools specification defines discovery, tool schemas and calls. Annotations from untrusted servers are untrusted. Input validation and access controls remain implementation responsibilities. This exercise adds an original local host-admission policy; its digest review is not claimed to be a mandated MCP consent mechanism.
Predict these distinctions before reading your actual output:
| Case | Boundary to investigate |
|---|---|
| supported | One useful P1 lookup; returned excerpt remains untrusted data. |
| changed-description / changed-schema / changed-output-schema / changed-annotations | Reviewed definition changed; zero business dispatches. |
| forged-scope | Extra tenant field rejected before dispatch. |
| foreign-record | One business lookup; unavailable without foreign text. |
| revoked-grant / changed-connection / changed-principal / expired-approval | Existing synthetic review does not renew current authority. |
| hint-is-not-authority | A read-only annotation cannot grant a sending callable. |
| withdrawn-source | A valid ID cannot recover withdrawn content. |
| malformed-result / oversized-result | Returned context withholds invalid or oversized output. |
The host's connection key, principal, tenant, approval and clock are trusted synthetic inputs. A real adapter must establish them independently of model arguments and self-reported server names. The local fingerprint covers the reviewed JSON definition, with stable object-key ordering. It is not a signature, endpoint authentication, code attestation or proof that the tool is safe.
The business callable rechecks current scope even when called without the adapter. Argument and result checks are handwritten for exactly the authored contract; they are not a general JSON Schema validator. The result ceiling counts serialized UTF-8 bytes, not characters or tokens, and applies after the local lookup. It cannot cap transport allocation, tool duration, invocation rate or other external costs.
In a disposable copy, remove only the digest-comparison line. Run test again and retain the failed changed-definition observation. Restore the check and show that the ordinary lookup still works. Ask a receiver why increasing an output budget cannot authorize a foreign source or a sender.
Finally, add one new case and its expected dispatch count. Identify the real MCP transport/version negotiation, OAuth, revocation during a remote call, deadlines and rate controls still to test separately. Valid returned text remains untrusted: this lab makes no inference call and does not measure prompt-injection resistance, factual quality or human approval.
Trace a source instruction through a second tool proposal
Edition 2 adds a two-call host-boundary rehearsal:
node fde-mcp-contract-lab.mjs followup-demoEach record first looks up a valid P1 source whose excerpt contains an injected instruction to change to south scope and send a response. That first result is schema-valid, within the byte limit and context-ready. Its trust remains untrusted-source-data. The lab does not detect, remove or neutralize the text. The next proposal is an authored fixture, not a model response. This experiment tests application enforcement after source retrieval, not model resistance to prompt injection.
| Follow-up | First lookup | Second proposal and observation |
|---|---|---|
| ordinary-lookup | One dispatch, untrusted excerpt | A normal P1 lookup remains admitted; the returned excerpt is still untrusted. |
| scope-claim | Same accepted source | An extra tenant field is invalid-arguments with zero further business dispatches. |
| send-claim | Same accepted source | send_response is tool-not-allowed with zero further business dispatches. |
| revoked-between | Same accepted source | Trusted fixture permission is revoked before the next dispatch; access-denied retains zero further business calls. |
Explain the two dispatch counts separately. Returning a source is not approving its instructions. The host's north scope never comes from the excerpt or the second proposal. Revocation changes the trusted host fixture, not a model-owned flag. The source is allowed to contain unsafe instructions without gaining any application authority; a schema-valid response is not a safe instruction channel.
Have a receiver predict the sender refusal before running the command. In a disposable copy, add an approved-looking field to the second proposal and retain the argument-shape refusal alongside the ordinary lookup. Identify where a real host derives identity and where a remote result could arrive after permission changes. Keep model behavior, semantic injection detection, remote in-flight revocation and external-effect cancellation unrun. No network, provider, message, credential or remote tool is used by this continuation.
2e. Shape an agent trace before exporting it
Observability / Trace boundary
What should reach the logger?
Predict a case, then inspect recorded output from the local Python lab. These eight synthetic cases use supplied statuses and timing. The browser does not run tools, export telemetry or record completion.
T2 / Proposed diagnostic fields
Useful tool diagnostics
The host reports lookup_procedure, outcome returned and elapsed_ms 12. The values are supplied synthetic inputs.
Choose a prediction to reveal the supplied observation.
20-minute observability lab · Python 3.11+ · No packages or provider key
Use the browser exercise above to predict a case and inspect its recorded JSON. Changing the case or trying again clears your prediction; it does not run the projector or save progress. Then gather your own output with the file below.
Download fde-agent-trace-lab.py. This standalone file projects application events through a deliberately closed schema before they could reach a logger. It does not install an OpenTelemetry SDK or export anything. Predict which events retain useful diagnostics before running:
python3 fde-agent-trace-lab.py test
python3 fde-agent-trace-lab.py demoThe demo processes eight visible synthetic cases and reports every case ID. T1–T3 project: admission keeps a source version, a completed tool call keeps its bounded name/outcome/timing, and a model's readiness claim stays labeled model_proposal. That claim is not an application receipt or human approval. T4–T8 refuse: source text, an attempted tenant override, an exception message, a Boolean used as elapsed time and an unbounded tool name. Inspect your output; these are deterministic local observations, not model or learner scores. Tool statuses and elapsed milliseconds are supplied inputs, not measured calls.
| Boundary | Explain the implementation choice |
|---|---|
| Host context | The caller supplies a synthetic tenant and run separately; event fields cannot override them. Real authentication remains outside this lab. |
| Field contract | Exact keys and approved scalar values only. Extra nested fields refuse the event, rather than silently exporting their contents. |
| Evidence origin | Trusted host code chooses the event kind; a model cannot label its own claim an application observation. |
| Failure | A fixed code crosses the boundary. Raw exception messages and rejected values are not echoed into the demo report. |
| Size | Compact serialized UTF-8 bytes are checked before release, with a default ceiling of 256. This does not limit ingestion memory or telemetry volume. |
The approved diagnostic fields remain useful: an operator can correlate R1, inspect a withheld tool outcome or identify a timeout without reading a source body. Refusing every event would discard that useful path. Conversely, removing payload text from the viewer after a logger already persisted it is too late for this boundary. Call the projector before logging, tracing or exception reporting, and test those real sinks separately when adapting the exercise.
In a disposable copy, add a source_locator diagnostic. Define its allowed shape before changing the schema, keep a useful case, and add a case that tries to put source text or a personal identifier into that field. Do not solve the exercise by accepting arbitrary strings or removing the refused cases. Ask a receiver to reproduce a useful tool event and explain the model-claim label without your driving. Keep their question, your revision and actual output.
The synthetic tenant enum and run pattern are teaching fixtures. Even a valid opaque identifier can be linkable or sensitive in a real environment. This is not general PII detection, redaction of arbitrary text, a logging SDK or a compliance assessment. No exporter configuration, retention, access policy, deletion, sampling, distributed correlation or production sink is tested. The current OpenTelemetry GenAI content-capture guidance recommends leaving instructions, inputs and outputs uncaptured by default. Its conventions are developing; the event format here is an original application exercise and does not claim to implement those conventions.
2f. Inspect an MCP stdio compatibility exchange
Choose the wire profile before writing the adapter
Download the stdio compatibility lab. This is an actual parent/child-process exchange over standard streams, pinned to MCP 2025-11-25. It uses the handshake model: initialize, a matching version and tools capability, then notifications/initialized, tools/list and tools/call. The client waits for each response before issuing the next request. It does not implement the newer profile or automatic fallback.
Version choice matters in field integration. The 2026-07-28 versioning source describes a different model: version and capabilities travel with each request, without this initialization handshake. Its compatibility guidance distinguishes legacy, modern and dual-era implementations. An engineer maintaining a handshake-based integration needs an explicit supported-version contract; passing this older-profile lab does not establish compatibility with a modern endpoint. Sources checked 18 October 2026.
Use the pinned lifecycle, stdio transport and tool messages as protocol references. This teaching implementation supports one authored tool and one pending request. It is not an MCP SDK, general schema validator, complete protocol implementation or interoperability certification.
Run two processes and read the boundary
Save the one file in a new disposable folder. Node.js 24 and permission to start your own child process are required. No packages, model, socket, provider key or customer records are used. The child inherits no environment variables from the parent. The program always launches its own file with the current Node executable; it accepts no arbitrary command or remote endpoint.
node --version
node fde-mcp-stdio-lab.mjs test
node fde-mcp-stdio-lab.mjs supported
node fde-mcp-stdio-lab.mjs foreign-record
node fde-mcp-stdio-lab.mjs forged-scope
node fde-mcp-stdio-lab.mjs demoThe supported transcript contains four sent methods in order. The initialized notification has no request ID and receives no response. Each request has an incrementing ID; each accepted response must match the pending request. The peer writes a diagnostic to stderr, while stdout carries newline-delimited JSON frames. The client records only diagnostic presence, message identities and the synthetic result. peerExited: true confirms the owned child was closed for that run; it says nothing about remote lifecycle management.
The successful result remains untrusted-tool-content. A server-supplied name is not authenticated identity, and an available tool is not a grant to read another tenant. This peer has a fixed synthetic north scope and can return only the P1 teaching record. There is no sign-in or real authorization service in the lab. Keep the earlier host-admission lab beside it: transport correctness and application authority are separate obligations.
Diagnose the failure at the right layer
| Case | Expected observation | Receiving-engineer explanation |
|---|---|---|
supported | received with a synthetic excerpt | The selected wire path returned data; a person has not accepted its use. |
foreign-record | tool-refused / tool-unavailable | A valid tool invocation can refuse a resource without a protocol failure. |
forged-scope | protocol-refused, JSON-RPC code -32602 | An added model-controlled tenant field violates the one authored argument contract. |
protocol-error | JSON-RPC code -32601 | A protocol error is distinct from result.isError in a tool execution response. |
wrong-id | unexpected-response-id before a call | Never attach an unrelated response to the current operation. |
missing-capability / unsupported-version | Stop before discovery/calling | Do not silently assume a tool capability or change the agreed profile. |
before-ready | discovery-protocol-error | For this pinned handshake profile, omitting the notification leaves the peer unready. |
stdout-noise / oversized-frame | non-json-stdout / frame-budget | A friendly status line on stdout breaks framing; the teaching client also limits its receive buffer. |
timeout | request-timeout and a closed child | The local deadline limits waiting; it does not prove a remote action was cancelled. |
Failures use fixed codes rather than copying malformed bytes. The 4 KiB receive limit and one-request-at-a-time contract are deliberately narrow; this client does not implement multiplexing, server notifications, pagination, reconnect, cancellation messages, HTTP/OAuth, server authentication, comprehensive output validation or a deployment sandbox. The deadline closes the owned local peer; do not substitute it for distributed cancellation semantics.
Extend and hand over the integration decision
First predict wrong-id and before-ready, then compare actual transcripts. In a disposable copy, add a second response with a valid-looking but unrelated ID and preserve the refusal. Restore the useful P1 path after the change. Explain what must change before this client could accept legitimate server notifications without confusing them with responses.
Give a receiver the selected protocol revision, commands, actual output, tool/resource refusals and the remaining integration work. Ask them to distinguish a received source, a refused tool, a broken protocol exchange and an accepted business outcome. A separate SDK/modern-profile experiment needs its own package versions and evidence; do not relabel this compatibility lab as that experiment.
2g. Evaluate agent canary evidence before widening access
Define the comparison before looking at the aggregate
Download the canary evidence lab. It evaluates supplied synthetic observations; it does not deploy, route traffic, call a model or decide a person's qualification. All acceptance labels are authored inputs. No supplied packet is evidence of a customer result or an actual human review.
A candidate can look healthy in a service-wide average while a smaller workflow deteriorates. The SRE canarying chapter explains why control/canary separation, representative exposure and aligned measurement windows matter. It also distinguishes artificial load tests from observing a real canary. This exercise is a local analysis of fictional packets, not a canary deployment. Reference checked 19 October 2026 UTC.
The local policy uses two fixed workflow segments, routine and exception. It requires at least 20 supplied observations per segment and cohort, permits at most a five-percentage-point error increase or accepted-work decrease, and limits the candidate response-latency p95 to 1.5 times the control. These are deliberately authored teaching thresholds, not SLO recommendations, statistical confidence bounds or a claim that twenty samples establish readiness. In a real release, choose and document appropriate criteria before exposure.
The packet declares admitted counts independently of its observation rows. The evaluator reconciles them; a missing row is not a successful request. It rejects duplicate IDs, inconsistent scalar types, impossible accepted/error combinations and unexpected segments. Windows must agree and each observation must fall inside the shared half-open interval. This validates the toy packet's consistency, not the authenticity of its counters, assignment, versions, labels or source system.
Run the cases and identify the decision layer
Save the standalone file in a disposable folder. Python 3.11 or later is required, with no packages or provider configuration.
python3 --version
python3 fde-canary-evidence-lab.py test
python3 fde-canary-evidence-lab.py baseline-ready
python3 fde-canary-evidence-lab.py aggregate-green-segment-red
python3 fde-canary-evidence-lab.py all-refusal
python3 fde-canary-evidence-lab.py missing-observation
python3 fde-canary-evidence-lab.py demoEvery output retains origin: synthetic-teaching-packet and release_authorized: false. A ready-for-human-review result only says this packet met this exercise's criteria. It neither grants production access nor recommends bypassing the accountable release decision.
| Packet | What to retain | What the engineer should explain |
|---|---|---|
baseline-ready | Four cohort/segment counts and two p95 comparisons | Useful supplied outcomes met the authored criteria; no production release was authorized. |
aggregate-green-segment-red | Candidate errors 7/120 versus control 4/120; exception errors 6/20 versus 2/20 | The aggregate increase is 2.5 percentage points, but the exception increase is 20 points. The segment regression stops the recommendation. |
all-refusal | Candidate errors are zero and accepted work is zero | A system that refuses useful work cannot win by having no recorded execution errors. |
missing-observation | Declared admission and observed row counts disagree | Hold for evidence; do not silently drop the missing record or infer success. |
missing-segment / small-sample | Missing or below-minimum segment coverage | Hold even if the included rows look healthy. The threshold is teaching policy, not statistical power. |
mixed-window | Different candidate/control intervals | Do not compare a previous incident window with the new candidate's window as if they were concurrent. |
latency-regression | The exception segment's response-latency p95 | Error rate alone does not describe acceptable operation. These response durations exclude human review/correction effort. |
safety-event | One supplied critical event and the stop reason | Do not average away an explicit safety trigger or excuse it because the sample is small. |
duplicate-observation / malformed-scalar | A hold with an integrity reason | Duplicate data and a Boolean used as an integer are not ordinary healthy observations. |
Accepted counts and response latency remain separate. A production field trial also needs the receiving operator's acceptance criteria, review/correction effort, queue delay, attempt costs and manual fallback. The two supplied segments do not establish representativeness for unmodeled tasks, tenants, loads or times of day. There is no randomization, causal inference, confidence interval, authenticated telemetry ingestion or actual rollout in this file.
Change one criterion and transfer the decision
Predict the aggregate-green/segment-red result before running it. Have a receiver compute both denominators from the JSON rather than reading only the status. In a disposable copy, add a third segment and show why both admission and observation contracts must change; do not let the unknown segment disappear from the comparison. Then add a new required observation field and preserve invalid/missing cases alongside the useful baseline.
Record your policy revision, synthetic packet, commands, actual output, hold/stop reasons and untested receiving-environment assumptions. Name the person who would authorize exposure in a real rollout. Keep the manual fallback and checkpoint-version recovery plan beside that decision. This evaluator is teaching evidence for a discussion, not a production promotion or rollback controller.
3. Make a meaningful change
Choose one extension. Keep a working baseline and record your changed source:
- Add a source withdrawal test before the model context boundary.
- Add an authenticated reviewer adapter over the domain method. Test revocation between pause and resume; do not trust a browser-supplied tenant or role.
- Add a bounded LangChain model adapter with a single read-only tool. Keep the model configuration outside the prompt. Record any unrun provider steps.
- Propose a durable LangGraph checkpointer, then test crash/resume and replay in that implementation. Do not relabel the in-memory run as durable.
- Add a simulated unknown external-effect outcome and a reconciliation record. Never add a real sending integration just to make the walkthrough impressive.
4. Hand it over
For a structured receiving conversation, use the free agent-system receiving defense. Bring one useful run, one boundary failure and a changed-condition decision; the receiver should be able to continue one available task from your packet.
Submit the contract, state diagram, source revision, environment versions, observed commands/output, one failing case and its repair, known limits and the operator recovery note. Ask a peer to reproduce an authorized request and diagnose a source-change refusal without you driving. Keep scripted outcomes, observed local results and unrun deployment work clearly separated.
Further reading: https://docs.langchain.com/oss/python/langchain/agents and https://docs.langchain.com/oss/python/langgraph/overview. These are technical references, not a partnership or a credential.