Skip to content

Add Lesson 18: Securing AI Agents with Cryptographic Receipts (closes #503)#533

Merged
koreyspace merged 3 commits intomicrosoft:mainfrom
tomjwxf:lesson-18-securing-ai-agents
Apr 30, 2026
Merged

Add Lesson 18: Securing AI Agents with Cryptographic Receipts (closes #503)#533
koreyspace merged 3 commits intomicrosoft:mainfrom
tomjwxf:lesson-18-securing-ai-agents

Conversation

@tomjwxf
Copy link
Copy Markdown
Contributor

@tomjwxf tomjwxf commented Apr 25, 2026

Summary

Adds the Lesson 18 content @leestott green-lit in #503. Python-first hands-on lesson on cryptographic receipts for agent tool calls.

Contents

  • 18-securing-ai-agents/README.md (~2,800 words) covering the audit-trail problem, what a receipt is, signing, verification, chaining, the boundary of what receipts prove, and production references. Includes two Mermaid diagrams (flow + chain), a five-question Knowledge Check, and a six-item Production Checklist.
  • code_samples/18-signed-receipts.ipynb (30 cells, four self-contained sections covering signing → tamper detection → chain integrity → tool wrapper).
  • code_samples/requirements.txt (PyNaCl + jcs + ipykernel only; no Azure dependency required to run the lesson).
  • code_samples/sample_receipts/ (three pre-generated receipt JSONs for inspection without running the notebook, plus a reproducible regeneration script).
  • Root README.md updated to swap the "Coming Soon" entry for the link.

Pedagogical choices

The lesson teaches the cryptographic primitive directly in plain Python (about 50 lines of signing + verification logic students can read line by line) rather than leading with any specific tool. Production tools are referenced at the end of the lesson, not the centerpiece, so students leave with a foundational understanding rather than a tool dependency.

The lesson explicitly delineates what receipts prove (attribution, integrity, ordering) versus what they do not (correctness, policy compliance, identity beyond the key). This boundary is the single most important takeaway and is repeated in both the README and the notebook recap.

Test plan

  • All four notebook sections execute without error on a clean Python 3.13 venv with pip install -r requirements.txt. Verified via jupyter nbconvert --to notebook --execute.
  • Receipt verification correctly returns True for valid receipts and False for any tampered receipt.
  • Chain verification correctly identifies the exact receipt at which tampering occurred.
  • ReceiptedTool wrapper produces a verifiable chain when called multiple times.
  • Sample fixtures verify (01 → True, 02 → False, 03 → all three chain links match).
  • README has zero em dashes (style consistency check).
  • Notebook nbformat-validated (30 cells, normalized cell IDs).

Open questions for reviewers

  1. Lesson number: used 18- to match the existing slot in the curriculum table's "Coming Soon" row. If 17 or 19 fits better, trivial to renumber.
  2. README scope: included a Production Checklist and a Knowledge Check beyond the core narrative. If you prefer a tighter lesson without those, happy to trim either or both.
  3. Sample fixtures folder: optional teaching aid for students who want to inspect a real receipt JSON without running the notebook. Happy to remove if outside scope.
  4. Thumbnail and video: standard placeholders left in place. Will produce a thumbnail or defer to the Microsoft content team per the usual lesson pattern (Lessons 14, 15 also have empty video columns).
  5. Next Lesson link: currently (To be determined by curriculum maintainers). Happy to update once the next lesson is confirmed.

CLA: previously confirmed via earlier contributions to microsoft/agent-governance-toolkit.

🤖 Generated with Claude Code

Closes microsoft#503.

Adds a complete lesson covering Ed25519-signed receipts for AI agent
tool calls, with four hands-on exercises:
1. Sign and verify a receipt for a single tool call
2. Tamper with a receipt and observe verification fail
3. Build a hash-chained sequence of receipts
4. Wrap a tool function with automatic receipt signing

The lesson is Python-first (matching the curriculum convention) and uses
only widely-available primitives: PyNaCl for Ed25519, the jcs package
for RFC 8785 canonical JSON, hashlib for SHA-256. The cryptographic
logic is plain Python that students can read line by line.

Production-tool references (protect-mcp, @veritasacta/verify, the
agent-governance-toolkit Tutorial 33) are mentioned at the end as next
steps, not as the centerpiece. The lesson teaches the primitive
directly.

The lesson explicitly delineates what receipts prove (attribution,
integrity, ordering) versus what they do NOT prove (correctness, policy
compliance, identity beyond the key). This boundary is treated as the
most important single takeaway and is repeated in both the README and
the notebook recap.

Includes:
- README.md (~2,800 words, two Mermaid diagrams, Knowledge Check
  section, Production Checklist appendix)
- code_samples/18-signed-receipts.ipynb (30 cells, 4 self-contained
  sections, nbformat-validated)
- code_samples/requirements.txt (PyNaCl + jcs + ipykernel)
- code_samples/sample_receipts/ (three pre-generated fixtures plus
  reproducible regeneration script)
- Root README.md updated to swap the "Coming Soon" entry for a link.

Verified end-to-end on a fresh Python 3.13 venv via
jupyter nbconvert --execute.
@github-actions
Copy link
Copy Markdown
Contributor

👋 Thanks for contributing @tomjwxf! We will review the pull request and get back to you soon.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Lesson 18 (“Securing AI Agents with Cryptographic Receipts”) to the curriculum, including a Python-first written lesson, hands-on notebook, and sample receipt fixtures for offline inspection.

Changes:

  • Adds new Lesson 18 README explaining receipt signing, verification, and chaining (with diagrams, knowledge check, and checklist).
  • Adds a Jupyter notebook walking through signing → tamper detection → chain integrity → tool wrapper pattern.
  • Adds minimal Python dependencies + pre-generated receipt fixtures (and a generator script), and links the lesson from the root curriculum README.
Show a summary per file
File Description
README.md Updates the curriculum table to link to the new Lesson 18.
18-securing-ai-agents/README.md New written lesson content (concepts, examples, diagrams, exercises).
18-securing-ai-agents/code_samples/requirements.txt Dependency list for running the Lesson 18 notebook.
18-securing-ai-agents/code_samples/18-signed-receipts.ipynb Hands-on notebook implementing signing/verifying/chaining + a tool wrapper.
18-securing-ai-agents/code_samples/sample_receipts/README.md Explains the included JSON fixtures and how to verify them.
18-securing-ai-agents/code_samples/sample_receipts/generate_fixtures.py Script to regenerate the receipt JSON fixtures reproducibly.
18-securing-ai-agents/code_samples/sample_receipts/01_valid_receipt.json Pre-generated valid receipt fixture.
18-securing-ai-agents/code_samples/sample_receipts/02_tampered_receipt.json Pre-generated tampered receipt fixture.
18-securing-ai-agents/code_samples/sample_receipts/03_chain_three_receipts.json Pre-generated 3-link receipt chain fixture.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (2)

18-securing-ai-agents/README.md:165

  • The verify_receipt example here assumes top-level signature and public_key fields and strips both from the signed payload, but the lesson’s actual receipts embed public_key under signature (and the signature field is an object). As written, this verification snippet won’t work with the provided fixtures/notebook output; update it to match the real receipt shape and base64url decoding approach used in the notebook (including correct padding handling).
def verify_receipt(receipt: dict) -> bool:
    # Separate the signature from the payload
    signed_payload = {k: v for k, v in receipt.items()
                      if k not in ("signature", "public_key")}

    # Recompute the canonical hash
    canonical_bytes = canonicalize(signed_payload)
    message_hash = hashlib.sha256(canonical_bytes).digest()

    # Decode the signature and public key
    signature = base64.urlsafe_b64decode(receipt["signature"] + "==")
    public_key = signing.VerifyKey(
        base64.urlsafe_b64decode(receipt["public_key"] + "==")
    )

18-securing-ai-agents/code_samples/18-signed-receipts.ipynb:446

  • verify_chain claims to check that sequence numbers increase monotonically, but the implementation requires receipt["sequence"] == i (exactly equal to the receipt’s position). Either relax the check to true monotonic increase (e.g., > previous) or adjust the docstring to state that sequences must be contiguous starting at 0, since the current mismatch can confuse learners.
    "      3. Sequence numbers must increase monotonically.\n",
    "    Returns a per-receipt result dict.\n",
    "    \"\"\"\n",
    "    results = []\n",
    "    for i, receipt in enumerate(chain):\n",
    "        sig_ok = verify_receipt(receipt)\n",
    "\n",
    "        if i == 0:\n",
    "            chain_ok = receipt[\"previous_receipt_hash\"] is None\n",
    "        else:\n",
    "            expected = receipt_hash(chain[i - 1])\n",
    "            chain_ok = receipt[\"previous_receipt_hash\"] == expected\n",
    "\n",
    "        seq_ok = receipt[\"sequence\"] == i\n",
  • Files reviewed: 9/9 changed files
  • Comments generated: 6

Comment thread 18-securing-ai-agents/README.md Outdated
Comment on lines +1 to +3
[![Securing AI Agents with Cryptographic Receipts](./images/lesson-18-thumbnail.png)](https://youtu.be/PLACEHOLDER_VIDEO_ID)

> _(Click the image above to view video of this lesson)_
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

./images/lesson-18-thumbnail.png is referenced, but this lesson directory doesn't include an images/ folder. This will render a broken thumbnail link in the lesson README; add the expected image asset(s) (or update the path to a shared/valid thumbnail location).

Suggested change
[![Securing AI Agents with Cryptographic Receipts](./images/lesson-18-thumbnail.png)](https://youtu.be/PLACEHOLDER_VIDEO_ID)
> _(Click the image above to view video of this lesson)_
[Watch the lesson video: Securing AI Agents with Cryptographic Receipts](https://youtu.be/PLACEHOLDER_VIDEO_ID)
> _(Use the link above to view the video for this lesson)_

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed in 5d461ba.

I removed the [![...](...png)](youtube) markdown and replaced it with a plain
text link to the placeholder video URL, plus a note that the lesson video and
thumbnail can be added by the Microsoft content team post-merge. This mirrors
how lessons 14 and 15 ship without their own video assets in the lesson
directory, and it removes the broken-image render in the meantime. Happy to
swap in a real thumbnail file under 18-securing-ai-agents/images/ if the
content team would prefer that path.

Comment thread 18-securing-ai-agents/README.md Outdated
"timestamp": "2026-04-25T14:30:00Z",
"sequence": 47,
"previous_receipt_hash": "sha256:9d4e6a...",
"signature": "ed25519:c5af83..."
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The receipt schema in this README is inconsistent with the notebook + fixtures: here signature is shown as a string, but the notebook/fixtures use a signature object containing alg, sig, and public_key. Align the JSON example(s) with the actual schema used in 18-signed-receipts.ipynb and code_samples/sample_receipts/*.json to avoid confusing readers.

This issue also appears on line 151 of the same file.

Suggested change
"signature": "ed25519:c5af83..."
"signature": {
"alg": "ed25519",
"sig": "c5af83...",
"public_key": "ed25519-pub:8f3b2c..."
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5d461ba. The README's example receipt now matches the structured
signature object that the notebook and the JSON fixtures actually emit:

"signature": {
  "alg": "EdDSA",
  "sig": "c5af83...",
  "public_key": "8f3b2c..."
}

One small departure from the suggested patch: I kept "alg": "EdDSA" rather
than "ed25519" because EdDSA is the registered JOSE/COSE algorithm name
(JWA, RFC 8037) and matches the algorithm identifier already used in the
fixtures and the notebook output. Using EdDSA here also keeps this lesson
forward-compatible with curve25519 vs curve448 variants in the same family.
The public_key field is plain base64url with no ed25519-pub: prefix, again
matching the fixtures.

Both occurrences of the schema example (line 77 and the verify snippet around
line 151) now use the same shape.

Comment on lines +112 to +123
# Build the receipt payload (no signature yet)
payload = {
"type": "agent.tool_call.v1",
"agent_id": "contoso-travel-bot",
"tool_name": "lookup_flights",
"tool_args_hash": hash_args({"origin": "SYD", "dest": "LAX"}),
"result_hash": hash_result(flight_results),
"policy_id": "contoso-travel-policy-v3",
"timestamp": "2026-04-25T14:30:00Z",
"sequence": 47,
"previous_receipt_hash": prior_chain_link,
}
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signing example uses hash_args, hash_result, flight_results, and prior_chain_link, but these helpers/variables are not defined anywhere in the README, and their naming doesn’t match the notebook’s sha256_canonical(...) approach. Consider either defining these helpers in the README snippet or rewriting the snippet to use the same sha256_canonical + previous_receipt_hash variables used in the notebook so the code is runnable/copy-pastable.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5d461ba. The signing snippet is now copy-pastable and uses exactly
the same helpers the notebook introduces, so a learner can read either one
first and the other will look familiar.

Specifically:

  • Defined b64url_nopad(...) and sha256_canonical(...) inline in the
    snippet (the same helpers the notebook defines).
  • Replaced the undefined hash_args(...) / hash_result(...) calls with
    sha256_canonical(tool_args) / sha256_canonical(tool_result).
  • Replaced the undefined flight_results / prior_chain_link names with
    literal example values (tool_args = {...}, tool_result = [...],
    previous_receipt_hash: None for the first receipt).
  • The signing block now produces the structured signature object directly,
    matching the schema example earlier in the README and the fixtures.

The snippet is short enough to be readable on the page and runnable as a
standalone Python file given the dependencies in requirements.txt.

Comment on lines +428 to +434
"def verify_chain(chain: list) -> dict:\n",
" \"\"\"\n",
" Verify a sequence of receipts:\n",
" 1. Each receipt's signature must verify.\n",
" 2. Each receipt (except the genesis) must reference the previous receipt's hash.\n",
" 3. Sequence numbers must increase monotonically.\n",
" Returns a per-receipt result dict.\n",
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verify_chain’s docstring/annotation says it returns a dict and that sequence numbers must be monotonically increasing, but the function actually returns a list of per-receipt dicts. Update the return type in the signature/docstring so it matches the implementation.

This issue also appears on line 433 of the same file.

Suggested change
"def verify_chain(chain: list) -> dict:\n",
" \"\"\"\n",
" Verify a sequence of receipts:\n",
" 1. Each receipt's signature must verify.\n",
" 2. Each receipt (except the genesis) must reference the previous receipt's hash.\n",
" 3. Sequence numbers must increase monotonically.\n",
" Returns a per-receipt result dict.\n",
"def verify_chain(chain: list) -> list[dict]:\n",
" \"\"\"\n",
" Verify a sequence of receipts:\n",
" 1. Each receipt's signature must verify.\n",
" 2. Each receipt (except the genesis) must reference the previous receipt's hash.\n",
" 3. Sequence numbers must match each receipt's zero-based position in the chain.\n",
" Returns a list of per-receipt result dicts.\n",

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5d461ba.

  • Return annotation changed from dict to list[dict], matching what the
    function actually returns (one result dict per receipt).
  • Docstring item 3 changed from "Sequence numbers must increase
    monotonically" to "Sequence numbers must match each receipt's zero-based
    position in the chain", which is what seq_ok = receipt["sequence"] == i
    actually checks.
  • Docstring tail changed from "Returns a per-receipt result dict" to
    "Returns a list of per-receipt result dicts".

I went with the "tighten the docstring to match the implementation" option
rather than "relax the check", because contiguous-from-zero is the property
the rest of the lesson relies on (the chain demo, the tampering exercise,
and the production checklist all assume sequence == position). A learner who
sees 0, 1, 2, ... in the fixtures gets a strictly stronger guarantee than
mere monotonicity, and there's no scenario in this lesson where receipts
arrive out of order or with gaps.

Comment on lines +546 to +551
" def __call__(self, **kwargs):\n",
" result = self.fn(**kwargs)\n",
" previous_hash = receipt_hash(self.receipts[-1]) if self.receipts else None\n",
" receipt = make_receipt(\n",
" tool_name=self.name,\n",
" tool_args=kwargs,\n",
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The text says ReceiptedTool can wrap “any callable tool function”, but __call__ only accepts keyword arguments and calls self.fn(**kwargs). Tools that require positional args (or *args, **kwargs) will fail; consider supporting *args and including both args/kwargs in the receipt input hash, or narrow the wording to keyword-only tools.

Suggested change
" def __call__(self, **kwargs):\n",
" result = self.fn(**kwargs)\n",
" previous_hash = receipt_hash(self.receipts[-1]) if self.receipts else None\n",
" receipt = make_receipt(\n",
" tool_name=self.name,\n",
" tool_args=kwargs,\n",
" def __call__(self, *args, **kwargs):\n",
" result = self.fn(*args, **kwargs)\n",
" previous_hash = receipt_hash(self.receipts[-1]) if self.receipts else None\n",
" receipt = make_receipt(\n",
" tool_name=self.name,\n",
" tool_args={\"args\": list(args), \"kwargs\": kwargs},\n",

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5d461ba. The call signature now accepts both positional and
keyword arguments and records both in the receipt:

def __call__(self, *args, **kwargs):
    result = self.fn(*args, **kwargs)
    ...
    receipt = make_receipt(
        tool_name=self.name,
        tool_args={"args": list(args), "kwargs": kwargs},
        ...
    )

This way:

  • A keyword-only call records {"args": [], "kwargs": {...}}, so existing
    notebook examples that use kwargs are unaffected.
  • A positional call records {"args": [...], "kwargs": {}} and is bound
    into the canonical hash, so tampering with the args in the receipt still
    invalidates verification.
  • Mixed-style calls record both.

The class docstring now states this explicitly: "Accepts both positional
and keyword arguments. The receipt's tool_args field records args (as a
list) and kwargs (as a dict) so the canonical hash binds to whichever the
caller supplied."

Section 4 of the notebook still uses kwargs in its example, which produces
exactly the same bytes-on-the-wire as before this change, so the fixtures
and the rest of the lesson are unaffected.

import hashlib
import json
import base64
from datetime import datetime, timezone
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

datetime and timezone are imported but never used (fixtures use the fixed FIXED_TS string). Removing unused imports will keep this fixture generator clean and avoid linter warnings.

Suggested change
from datetime import datetime, timezone

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5d461ba. Removed the from datetime import datetime, timezone
line. The fixture generator only ever uses the constant FIXED_TS string
to keep output byte-reproducible, so the datetime machinery was dead
weight.

Re-running the script after the change still produces byte-identical
fixtures (verified against the JSON files committed in 209eb6c).

@leestott
Copy link
Copy Markdown
Contributor

@tomjwxf please see comments above

Ensure the readme and sequential number is correct and the flow of the modules is complete

tomjwxf added 2 commits April 26, 2026 18:53
README.md
  * Replace broken thumbnail image link with a plain text link, mirroring
    the lesson 14 / 15 pattern, with a note that the Microsoft content
    team can swap in a thumbnail post-merge.
  * Update the example receipt JSON so signature is a structured object
    {alg, sig, public_key} matching the notebook output and the JSON
    fixtures under code_samples/sample_receipts/.
  * Rewrite the "Producing a Receipt in Python" snippet so it uses the
    same sha256_canonical helper and previous_receipt_hash variable
    introduced by the notebook. Removes the previously undefined
    hash_args, hash_result, flight_results, and prior_chain_link names
    so the snippet is copy-pastable.
  * Rewrite the "Verifying a Receipt" snippet so it reads the structured
    signature object (alg / sig / public_key) and uses an explicit
    b64url_decode helper for padding-safe base64url decoding.

code_samples/18-signed-receipts.ipynb
  * verify_chain: change return annotation from dict to list[dict] and
    update the docstring to say "Sequence numbers must match each
    receipt's zero-based position in the chain. Returns a list of
    per-receipt result dicts." This now matches what the function
    actually returns.
  * ReceiptedTool.__call__: accept *args in addition to **kwargs so
    tools that take positional arguments can still be wrapped. Record
    both via tool_args={"args": list(args), "kwargs": kwargs} so the
    canonical hash binds to whichever the caller supplied. Class
    docstring updated to explain the args / kwargs handling.

code_samples/sample_receipts/generate_fixtures.py
  * Remove the unused datetime / timezone imports (the script uses the
    fixed FIXED_TS string).

The notebook still executes end-to-end (jupyter nbconvert --execute,
all 30 cells pass) and the regenerated fixtures are byte-identical to
the previous fixtures committed in 209eb6c.
Three small additive improvements to point learners at the broader
receipt ecosystem without expanding what the lesson teaches.

README.md
  * New "Beyond This Lesson" section between Production Checklist and
    Additional Resources, with six bullets naming the next concepts in
    the same family: selective disclosure, receipt revocation,
    bilateral / split-signature receipts, payload composition, cross-
    implementation conformance, and post-quantum migration. Each is
    one paragraph; none requires the lesson to teach the underlying
    math or algorithm.
  * Practice Exercise: stretch challenge split into two. Stretch 1 is
    the existing "extend the schema with a custom field" challenge
    (unchanged). Stretch 2 introduces the Merkle-commitment pattern
    by having the learner SHA-256-hash two receipts together and
    embed the digest in a third, demonstrating a one-step inclusion
    proof using only primitives the lesson already taught.
  * Additional Resources: added two entries:
      - RFC 6962: Certificate Transparency (the Merkle-tree
        construction underpinning selective-disclosure receipts)
      - Cross-implementation conformance test vectors for the receipt
        format used in this lesson (Apache-2.0)

The notebook still executes end-to-end (jupyter nbconvert --execute,
all 30 cells pass) and the regenerated fixtures remain byte-identical.
@tomjwxf
Copy link
Copy Markdown
Contributor Author

tomjwxf commented Apr 26, 2026

Thanks for the careful review, Lee. Pushed two commits, 5d461ba (Copilot
fixes) and f1bcb4b (a small set of forward-pointing additions). Per-comment
replies above; quick summary of the changes:

5d461ba (Copilot review):

  • README: replaced the broken thumbnail markdown with a placeholder text
    link (matching the lesson 14 / 15 pattern), aligned the example receipt
    JSON with the structured signature object the notebook actually
    produces, and rewrote the signing and verification snippets so they use
    the same helpers the notebook introduces and are runnable as-is.
  • Notebook: tightened verify_chain's return annotation and docstring to
    match the implementation (list[dict], position-equals-sequence),
    and made ReceiptedTool accept both positional and keyword arguments
    while binding both into the canonical hash.
  • Fixture generator: removed the unused datetime imports.

f1bcb4b (forward-pointing additions, no scope expansion):

  • New "Beyond This Lesson" section with six one-paragraph bullets naming
    the next concepts in the same family (selective disclosure, receipt
    revocation, bilateral / split-signature receipts, payload composition,
    cross-implementation conformance, post-quantum migration). Gives the
    learner a map without expanding what this lesson teaches.
  • Practice Exercise: stretch challenge split into two. Stretch 2 has the
    learner SHA-256-hash two receipts together and embed the digest in a
    third, demonstrating a one-step inclusion proof using only primitives
    the lesson already taught. Previews Merkle commitments without
    introducing the math.
  • Two new Additional Resources entries: RFC 6962 (the Merkle-tree
    construction) and the cross-implementation conformance test vectors
    for the receipt format used in this lesson (Apache-2.0).

I re-ran the notebook end-to-end after each commit (jupyter nbconvert --execute, all 30 cells pass) and re-ran generate_fixtures.py to confirm
the JSON fixtures committed in 209eb6c are byte-identical.

On your two specific asks:

Sequential lesson number. Slot 18 was already reserved in the
curriculum table as "Securing AI Agents (Coming Soon)" before this PR
(visible in #503, where I asked to take that slot). The folder name
18-securing-ai-agents, the curriculum table row, and the lesson README
title all use the same number. Lessons 16 ("Deploying Scalable Agents")
and 17 ("Creating Local AI Agents") remain "Coming Soon" placeholders for
distinct topics. If the curriculum team would prefer to renumber this
content into one of those slots, I'm happy to rename the folder and
update the cross-links in a follow-up commit; otherwise 18 is the slot
that was advertised on the public roadmap.

Module flow. The lesson assumes lessons 1 to 11 as background
(specifically tool use from L4, MCP from L11, and the trustworthy-agents
framing from L6, all of which are referenced in the lesson README). It
adds a layer that the existing curriculum doesn't cover: how an external
auditor can verify what an agent did, without needing to trust the agent
operator. The Production Checklist at the end (Azure Key Vault, JWK Set
publication at .well-known, transparency-log anchoring, immutable
storage) explicitly hands the learner off to operational topics that
"Deploying Scalable Agents" (L16) and "AI Agents in Production" (L10)
cover. The "Previous Lesson" link points to L15 because L16 / L17 are not
yet authored; once they ship, that cross-link can be updated. "Next
Lesson" is left as "To be determined by curriculum maintainers" so the
team has full discretion over what comes after security in the final
sequence.

If there's appetite, two natural follow-up lessons exist that I would be
happy to draft as separate issues / PRs after this one merges:

  • Selective-disclosure receipts. Walks through the Merkle-commitment
    construction the "Beyond This Lesson" section names: revealing some
    receipt fields to specific auditors while keeping others
    cryptographically hidden. This is the most-asked-about advanced topic
    from the open-source receipt user community.
  • Bilateral / split-signature receipts. Walks through the pre-
    execution + post-execution split that some implementers use to bind
    authorization and result independently when the two are produced by
    different actors or at different times. Useful for high-assurance
    workflows where the auth decision is reviewable separately from the
    observed outcome.

Both topics are real, both are discoverable from the receipt format
introduced in this lesson, and neither is required for a beginners
audience to learn first. Happy to leave them entirely to the curriculum
team, propose them as separate issues, or skip them.

Otherwise the PR has been rebased to the new commits and CI should re-run
on each push.

@koreyspace koreyspace merged commit 0afb2c7 into microsoft:main Apr 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants