Skip to content
← Free FDE training guides

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

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

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

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

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

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

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

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

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

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

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

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

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

  13. 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 withPreparationEvidence to keep
Durable workflow referencePython standard library; one .py fileA paused draft, a fresh-process read and a bound decision.
Scoped retrievalPython standard library; standalone fileAuthorized evidence, content-edit invalidation and revoked access.
Regression comparisonWorkflow and evaluation .py files togetherReference, broken-candidate and missing-case reports.
MCP tool admissionNode.js 24; standalone .mjs fileDefinition change, current grant and argument/result boundary decisions.
MCP stdio compatibilityNode.js 24; standalone file; owned child processCorrelated wire frames, a useful tool result and protocol/tool refusals.
Agent canary evidencePython standard library; standalone fileSegment comparisons, incomplete evidence and a non-authorizing review recommendation.
Shared delegationNode.js 24; standalone .mjs fileConsumed attempts, freed slots and late-result refusal.
Worker recoveryPython standard library; actual SIGKILL extension on macOS/LinuxA committed lease after process death, bounded reclaim and current receipt.
LangGraph SQLite restartSeparate environment with three pinned packagesThe same real graph checkpoint inspected and resumed in fresh processes.
LangGraph wiringSeparate virtual environment and pinned packageAn observed interrupt and scripted resume; in-memory only.
LangChain tool loopSeparate virtual environment and pinned packagesProposed 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:

Code example
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.sqlite

The 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:

Code example
python3 fde-agent-engineering-lab.py inspect --db field-desk.sqlite --tenant south

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

1b. Build current, authorized retrieval context

RETRIEVAL / EVIDENCE COMPARISON

Inspect what the score leaves out.

Compare supplied reports from the downloadable Python lab. These eight cases are synthetic and visible. This page displays recorded observations without running retrieval, calling a model or recording completion.

Scoped retrieval

8 of 8 supplied cases observed

Mean recall at 2
0.60

3 query-recall sum / 5 answerable queries

Mean reciprocal rank at 2: 0.60

3 of 3 empty-evidence cases returned no evidence. Counted separately.

0 invalid passages · 0 context-budget violations

Global first, then filter

8 of 8 supplied cases observed

Mean recall at 2
0.10

0.5 query-recall sum / 5 answerable queries

Mean reciprocal rank at 2: 0.20

3 of 3 empty-evidence cases returned no evidence. Counted separately.

0 invalid passages · 0 context-budget violations

Global first, then filter: 8 of 8 cases observed. The selected packet is complete; no readiness claim follows. Selected case: D2 · Two passages required.

Does a useful first hit supply every required passage?

Development practice · Query: “check” · 400-character context ceiling

Scoped retrieval

2 passages returned · 2 relevant

Recall at 2
1.00
Reciprocal rank at 2
1.00

0 invalid passages · within context budget

Global first, then filter

1 passages returned · 1 relevant

Recall at 2
0.50
Reciprocal rank at 2
1.00

0 invalid passages · within context budget

Inspect source identities

These labels identify complete synthetic content snapshots. Read the Python file for the corpus; a matching identifier alone is not answer-support evidence.

Required passages
  • north / P4 · version 1 · paragraph:1a2c5ca12375b26d7efee19770fc3bffe983a23537de732a378212ceedaf740bd
  • north / P5 · version 1 · paragraph:17d12f7eb818a5a42f72d0947735c1b3d8b3eb88da2b7d76c5905ae3a9ac5cc23
Scoped retrieval returned
  • north / P4 · version 1 · paragraph:1a2c5ca12375b26d7efee19770fc3bffe983a23537de732a378212ceedaf740bd
  • north / P5 · version 1 · paragraph:17d12f7eb818a5a42f72d0947735c1b3d8b3eb88da2b7d76c5905ae3a9ac5cc23
Comparison returned
  • north / P4 · version 1 · paragraph:1a2c5ca12375b26d7efee19770fc3bffe983a23537de732a378212ceedaf740bd

Reproduce this comparison locally

python3 fde-retrieval-context-lab.py evaluate --candidate postfilter

Python 3.11+. A complete supplied packet exits 0, without declaring the candidate ready. The commands and metric worksheet continue below.

Take the evidence into a receiving conversation.

Keep this comparison, all eight case states and the reproduction commands in an editable Markdown brief. Add your explanation, actual-run observations and a receiver’s challenge in your own copy.

Download fde-retrieval-context-lab.py from the free lab page. This is a standalone standard-library exercise; it does not import the workflow file. Predict the two retrieval outcomes before running:

Code example
python3 fde-retrieval-context-lab.py test
python3 fde-retrieval-context-lab.py demo

The demo contrasts a deliberately flawed global-top-one/post-filter comparison with a builder that checks current source authorization before chunking and ranking. A foreign document contains more query-term occurrences, so the flawed comparison throws away its only result even though an authorized passage exists. The scoped builder preserves the useful task and keeps the foreign text out. All records are synthetic; the CLI principal stands in for trusted host context, not an authentication service. Real storage adapters must enforce the same scope.

This baseline ranks exact term frequency over paragraph chunks. It is not an embedding index or an answer generator. Inspect the returned tenant, source, version, paragraph locator and content digest. Then compare an unchanged cache hit with a text edit that keeps the old version number and with a revoked grant. The cache compares the entire currently eligible source snapshot, not only the previously selected hits, so newly permitted evidence can invalidate an empty lookup. This intentionally rescans eligible text; it is not a performance claim. The process-local dictionary has no expiry, eviction or deletion API. Revocation prevents reuse through the rechecked boundary but does not erase every old cache entry. Design and test retention separately before storing any real content.

The eight bundled tests also cover source withdrawal, copied result mutation, identity-scoped reuse, duplicate source identity, new evidence after an empty lookup and a character budget that keeps whole chunks. The ceiling covers selected evidence text, not metadata, tokens or a future model request. A withheld_budget result is distinct from no_matching_context; neither proves there is no answer in the corpus. The context_ready state means selected lexical evidence is present, not that a generated answer is supported.

Keep your actual JSON, a source revision and one new case the lexical baseline misses. Explain what a semantic retriever would need to improve, and what must remain invariant when replacing the ranker. The lab labels evidence untrusted but does not call a model or test prompt-injection resistance. Add inference, answer-support, hostile-source and receiving-engineer checks separately before making those claims.

Compare retrieval quality on a fixed packet

On the free lab page, use the interactive comparison to choose a candidate, inspect a case and predict its interpretation. The browser displays supplied local observations; use the command below to gather your own report. Use Download receiving brief in that comparison to keep the selected report, all case states and source snapshots in an editable Markdown worksheet. Fill in your own explanation and receiver challenge, then add actual local output when you run the lab. Preparing the worksheet does not mark a run or a course complete.

Use the same standalone file; no packages, embeddings or provider key are needed:

Code example
python3 fde-retrieval-context-lab.py evaluate --candidate scoped
python3 fde-retrieval-context-lab.py evaluate --candidate postfilter
python3 fde-retrieval-context-lab.py evaluate --candidate refuse-all
python3 fde-retrieval-context-lab.py evaluate --split development
python3 fde-retrieval-context-lab.py evaluate --split transfer

The eight visible synthetic cases have authored paragraph relevance labels. Inspect evaluation_cases() in your downloaded file before changing a candidate. Four development cases cover foreign ranking dominance, two required passages, withdrawal and revoked access. Four transfer cases cover different terminology, a withdrawn distractor, a foreign-only source and evidence that cannot fit its character budget. “Transfer” is a practice partition, not an independent hidden test. Candidates retrieve without receiving the relevance labels.

Each report retains case IDs, source snapshot identities, required and returned passages, character ceilings, per-query scores and explicit denominators. The context ceiling is two chunks; the flawed post-filter candidate still takes one global result before checking access. The same whole-chunk character budget is applied to its output. Do not feed that flawed comparison into model context.

FieldHow to interpret it
mean_recall_at_2For each executed answerable query, distinct required passages found divided by required passages; average those query recalls.
mrr_at_2Average the reciprocal of the first relevant rank within two results; an answerable miss contributes zero.
answerable_planned, answerable_executedKeep the original population separate from the queries actually run.
correct_empty, unanswerable_executedCount the supplied empty-evidence cases separately; they do not raise answerable recall.
invalid_evidence, context_budget_violationsCurrent source/identity/content checks and context ceilings remain independent of relevance.
unrun, completeName omissions. Complete means all selected cases ran, not that the candidate is ready to deploy.

On the complete supplied packet the scoped candidate has a recall sum of 3 over five answerable queries: mean recall 0.6. The three empty-evidence cases return empty. Development mean recall is 1 across two answerable queries; transfer is 1/3 across three. Keep the terminology miss and budget withholding in that latter denominator. These values describe this visible local packet only.

In D2 the post-filter candidate finds one of two required passages at rank 1. That query has recall 0.5 and reciprocal rank 1. Its complete-packet means are 0.1 recall and 0.2 reciprocal rank. An early useful hit is not complete evidence. The refuse-all control gets the three empty cases right but has zero answerable recall. Source identity includes the tenant, version, locator and content digest; stale or altered text cannot earn a valid hit, and duplicates do not multiply recall.

Now deliberately leave a miss unrun:

Code example
python3 fde-retrieval-context-lab.py evaluate --candidate scoped --omit-case T1-different-terminology

This produces JSON and intentionally exits 2. The mean becomes 0.75 over four executed answerable queries, but complete is false and the omitted case stays in unrun. That higher displayed average is not a retrieval improvement. Omit every answerable case and the metric denominator is zero: its value is null, not perfect. Keep the original complete reports before making a change.

Ask a receiver to add a new terminology query and an independently chosen relevant paragraph, then explain a miss without tuning the expected label to your candidate. Retain the case-level report and propose a new ranking or chunking experiment. This exercise evaluates a small lexical/context baseline, not semantic retrieval, answer support, a live model, prompt-injection resistance or enterprise performance. Relevance labels may be incomplete in real corpora; a representative evaluation needs separate labeling and sampling evidence. Ranked retrieval evaluation and the information-retrieval evaluator reference provide further metric context; this lab uses the explicit denominator policy above.

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:

Code example
python3 fde-worker-recovery-lab.py test
python3 fde-worker-recovery-lab.py demo

The 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:

ObservationNext safe decision to explain
A worker has a live leaseKeep a second worker from stealing it.
A lease expires and another worker reclaims itPreserve the increasing fencing number; reject stale completion.
Cancellation invalidates the leaseStop local acceptance; investigate any effect already in flight.
The attempt budget is exhaustedRetain the record and investigate; do not silently replenish attempts.
Rolled-back code cannot read the format markerKeep 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:

Code example
python3 fde-worker-recovery-lab.py crash-demo

This 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:

FieldObservation to explain
boundary.event, worker_exitlease_committed, then -9 on the supported POSIX run.
boundary.job, after_killIdentical stored lease; still running, with a null result digest.
integrity_checkSQLite returns ok for this local after-commit experiment.
before_expiry, attempts_before_reclaimlease_busy at injected tick 4; one attempt remains consumed.
reclaimedWorker B holds fence 2 at injected tick 5; two attempts are consumed.
reconstructed_stale_submissionA synthetic replay of A's old lease is refused as lease_lost.
finished, receipt_replay_equalB 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:

Code example
python3 fde-worker-recovery-lab.py race-demo

The 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 fieldObservation to explain
workersTwo distinct process IDs; one claimed, one lease_busy.
Winning fence1: only one claim was admitted.
after_claims.attempts1, not 2: the refused contender did not consume another attempt.
after_claims.staterunning; result_digest is still null. A lease is not completion.
integrity_checkok 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.

1d. Inspect a shared delegation budget

DELEGATION / TRACE WALKTHROUGH

One request.
One shared budget.

Step through supplied output from the downloadable coordinator. These are synthetic offline observations. No workers, model calls or external actions run here.

Does a failed attempt refund the shared allowance?

Attempts consumed
0 / 2 total
In flight
0 / 2 slots
Source
v3 · current

Before dispatch. Predict the next decision. Deadline: 1000 virtual ms.

  1. 100 ms
    Reserve worker · D1Decision hidden — step forward
  2. 120 ms
    Reserve worker · D2Decision hidden — step forward
  3. 400 ms
    Worker result · D1Decision hidden — step forward
  4. 450 ms
    Worker failure · D2Decision hidden — step forward
  5. 600 ms
    Reserve worker · D3Decision hidden — step forward
  6. 650 ms
    Worker failure · D3Decision hidden — step forward
  7. 675 ms
    Check candidate · D1Decision hidden — step forward
Download the coordinator · Node.js 24

Use the interactive trace on the free lab page before running the coordinator. Choose a case, predict the next decision, then use Next event to inspect the supplied observation. Previous and Restart trace let you revisit the reasoning. The browser replays recorded synthetic output; it does not dispatch workers, run a model or grade your work.

Download fde-delegation-lab.mjs, check Node.js 24 and run:

Code example
node --version
node fde-delegation-lab.mjs supported
node fde-delegation-lab.mjs failed-retry
node fde-delegation-lab.mjs source-withdrawn
node fde-delegation-lab.mjs terminal-manual
node fde-delegation-lab.mjs exact-deadline
node fde-delegation-lab.mjs --all

The final command prints 13 scenario records, one JSON object per line. Exit 0 means the replay ran; it does not mean every proposed action was permitted. No packages, API keys or network calls are needed. Keep the actual output.

In failed-retry, D1 and D2 consume both attempts. D1 returns at 400 virtual ms; D2 fails at 450. At that point, attempts consumed is 2 and in flight is 0. Failure releases a slot without refunding the shared allowance. D3 is refused at 600. Its message at 650 is unknown-attempt, not proof of a third execution.

candidate-held is intermediate. In supported, review rechecks the current source before producing a draft for operator review; nothing is sent. In source-withdrawn, the candidate arrives before withdrawal, but the later review cannot use it. In terminal-manual, the explicit manual decision closes the request before the result arrives. In exact-deadline, an event at 1,000 virtual ms is too late. These timestamps are injected inputs, not latency measurements.

For a changed-constraint experiment, use a disposable copy and change only maxAttempts: 2 to maxAttempts: 3. Predict and rerun failed-retry and foreign-scope. More budget can admit a retry; it cannot authorize foreign scope, revive a withdrawn source or reopen the terminal manual route. Ask a receiver to explain those boundaries from the recorded events.

The existing delegation workbook practice contains the fuller worker packet, conflicting references and receiving contract. Keep that material beside this trace. The local coordinator is single-process and scripted: it does not establish atomic distributed reservation, authenticated worker messages, real cancellation or model quality. A deployment needs isolated integration evidence for those boundaries.

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.

Code example
python3 fde-receiver-recovery-lab.py test
python3 fde-receiver-recovery-lab.py demo

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

BoundaryBefore retry: effects / receiptsAfter retry: effects / receipts
Before shared commit0 / 01 / 1
After shared commit, response missing1 / 11 / 1
Broken: effect committed alone1 / 02 / 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:

Code example
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.py

On 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:

Code example
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.sqlite

On 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:

ObservationWhat it establishes locally
First process: awaiting_review, next review, interrupted trueA pending graph checkpoint exists.
Second process: same digest and pending stateA fresh graph instance reads the saved work.
Third process: accepted, empty next, interrupted falseThe scripted bound decision resumed this local graph.
Fourth process: decision-replayedThe 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:

Code example
.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.sqlite

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

Code example
.venv-graph/bin/python fde-langgraph-durable-lab.py resume --db graph-practice.sqlite --decision accepted --digest PASTE_OBSERVED_DIGEST

Before 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:

Code example
.venv-graph/bin/python fde-langgraph-durable-lab.py failure-demo --db graph-failure.sqlite

Five 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:

ProcessNode calls in this processPending failureHuman interrupt
Deliberate failuredraft, reviewtruefalse
Fresh inspectionnonetruefalse
Retry without a new inputreviewfalsetrue
Bound accepted resumereviewfalsefalse
Same decision replaynonefalsefalse

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):

Code example
.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_DIGEST

Before 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:

Code example
.venv-graph/bin/python fde-langgraph-durable-lab.py parallel-demo --db graph-parallel.sqlite

Four 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:

ProcessNode calls in this processWhat to inspect
Scripted branch failuredraft + policy, order unspecifiedtask_results.draft true; policy error; no review interrupt
Fresh inspectionnoneSame draft digest and saved draft task result
Retry same thread with no new inputpolicy, reviewDraft does not repeat; policy checked; review interrupted
Bound accepted resumereviewAccepted 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:

Code example
.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_DIGEST

Before 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:

Code example
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 spin

On 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:

Code example
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-source

The 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:

CandidateWhat the declared contract should expose
referenceUtility 1/1, boundary 4/4, recovery 1/1; all six cases observed.
refuse-allForeign-source denial passes, but useful drafting fails; setup-dependent cases report failed observations.
stale-reviewUseful drafting and replay pass; changed and withdrawn source acceptance fail.
reference with omissionFive 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:

Code example
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 demo

The 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:

CaseBoundary to investigate
supportedOne useful P1 lookup; returned excerpt remains untrusted data.
changed-description / changed-schema / changed-output-schema / changed-annotationsReviewed definition changed; zero business dispatches.
forged-scopeExtra tenant field rejected before dispatch.
foreign-recordOne business lookup; unavailable without foreign text.
revoked-grant / changed-connection / changed-principal / expired-approvalExisting synthetic review does not renew current authority.
hint-is-not-authorityA read-only annotation cannot grant a sending callable.
withdrawn-sourceA valid ID cannot recover withdrawn content.
malformed-result / oversized-resultReturned 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:

Code example
node fde-mcp-contract-lab.mjs followup-demo

Each 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-upFirst lookupSecond proposal and observation
ordinary-lookupOne dispatch, untrusted excerptA normal P1 lookup remains admitted; the returned excerpt is still untrusted.
scope-claimSame accepted sourceAn extra tenant field is invalid-arguments with zero further business dispatches.
send-claimSame accepted sourcesend_response is tool-not-allowed with zero further business dispatches.
revoked-betweenSame accepted sourceTrusted 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.

Predict the boundary decision

Choose a prediction to reveal the supplied observation.

Recorded 2026-10-18 · Python 3.12.14 · 8 supplied cases

Reproduce your own output locally:

python3 fde-agent-trace-lab.py demo

Keep useful diagnostics and challenge a newly added field. This packet does not establish production privacy or general detection of personal data.

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:

Code example
python3 fde-agent-trace-lab.py test
python3 fde-agent-trace-lab.py demo

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

BoundaryExplain the implementation choice
Host contextThe caller supplies a synthetic tenant and run separately; event fields cannot override them. Real authentication remains outside this lab.
Field contractExact keys and approved scalar values only. Extra nested fields refuse the event, rather than silently exporting their contents.
Evidence originTrusted host code chooses the event kind; a model cannot label its own claim an application observation.
FailureA fixed code crosses the boundary. Raw exception messages and rejected values are not echoed into the demo report.
SizeCompact 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.

Code example
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 demo

The 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

CaseExpected observationReceiving-engineer explanation
supportedreceived with a synthetic excerptThe selected wire path returned data; a person has not accepted its use.
foreign-recordtool-refused / tool-unavailableA valid tool invocation can refuse a resource without a protocol failure.
forged-scopeprotocol-refused, JSON-RPC code -32602An added model-controlled tenant field violates the one authored argument contract.
protocol-errorJSON-RPC code -32601A protocol error is distinct from result.isError in a tool execution response.
wrong-idunexpected-response-id before a callNever attach an unrelated response to the current operation.
missing-capability / unsupported-versionStop before discovery/callingDo not silently assume a tool capability or change the agreed profile.
before-readydiscovery-protocol-errorFor this pinned handshake profile, omitting the notification leaves the peer unready.
stdout-noise / oversized-framenon-json-stdout / frame-budgetA friendly status line on stdout breaks framing; the teaching client also limits its receive buffer.
timeoutrequest-timeout and a closed childThe 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.

Code example
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 demo

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

PacketWhat to retainWhat the engineer should explain
baseline-readyFour cohort/segment counts and two p95 comparisonsUseful supplied outcomes met the authored criteria; no production release was authorized.
aggregate-green-segment-redCandidate errors 7/120 versus control 4/120; exception errors 6/20 versus 2/20The aggregate increase is 2.5 percentage points, but the exception increase is 20 points. The segment regression stops the recommendation.
all-refusalCandidate errors are zero and accepted work is zeroA system that refuses useful work cannot win by having no recorded execution errors.
missing-observationDeclared admission and observed row counts disagreeHold for evidence; do not silently drop the missing record or infer success.
missing-segment / small-sampleMissing or below-minimum segment coverageHold even if the included rows look healthy. The threshold is teaching policy, not statistical power.
mixed-windowDifferent candidate/control intervalsDo not compare a previous incident window with the new candidate's window as if they were concurrent.
latency-regressionThe exception segment's response-latency p95Error rate alone does not describe acceptable operation. These response durations exclude human review/correction effort.
safety-eventOne supplied critical event and the stop reasonDo not average away an explicit safety trigger or excuse it because the sample is small.
duplicate-observation / malformed-scalarA hold with an integrity reasonDuplicate 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.

Before you move on

Keep the evidence of your work.

  • Actual local commands, package versions and observed results.
  • A comparison retaining useful work, boundary failures and unrun cases.
  • One requirement-driven extension and an operator handover note.

Keep these in your own working copy. Reading this page does not save work or record completion.

Suggested next free session

Plan cohort review capacity

Plan the human review capacity that dependable delivery needs. This is a suggested sequence; choose the practice that addresses your current gap.

Open the next free guide
Have Academy access?

Continue with worked explanations, practice and review in the related lesson.

Open the related Academy lesson →
Back to top ↑