Tutorial
CS357: Foundations of Artificial Intelligence - Handoff Protocols: Claims, Stale Claims, and a Handoff Skill for Your Project
To practice coordinating two agents through a durable medium on a small problem first, and then to write a handoff SKILL.md for a project repository of your own choosing.
Contents
About This Tutorial
Two agents are pointed at the same task list. Neither can see the other’s context window. Agent A starts a task, and an hour later Agent B looks at the same list. How does B know the task is taken? What if A crashed halfway through? And when B finds a file in done/, how does it know the work was finished rather than abandoned? This tutorial answers those questions on a small practice problem first. Then you write the answers down as a handoff skill for a real repository.
Part I reviews start, stop, and restart, the three moments every agent session has. Part II states the four rules of a claim protocol, separates the rules the medium enforces from the ones that are only advisory, and adds one trace line per action. Part III is practice in isolation, on one of two paths. The code path is a pure-Python claim race between two agents over a folder, and it needs nothing but python3. The no-code path runs the same protocol by hand with a GitHub issue or a shared folder. In Part IV, you write a SKILL.md for a project repository you choose. Your team’s final project repository is one option, and your own cs357-work repository is another.
This page builds on Agents That Talk, Observability, and Handoff Protocols (Parts II and IV). It reuses that session’s file names, its claim race, and its SKILL.md skeleton. Nothing on this page is collected. You need one path in Part III, not both. The skill in Part IV is the part worth keeping.
Key Concepts
| Term | Plain-English Definition | Where You’ll Meet It |
|---|---|---|
| Durable medium | Something outside both agents that survives either one crashing: a repository, an issue thread, or a folder on disk | handoff/ with inbox/, claimed/, and done/, or GitHub issue #42 |
| Claim | A mark, visible to the other agent through the medium alone, that says “this item is mine and I started it at this time” | Renaming a file into handoff/claimed/ and writing task-7.md.claim; assigning yourself an issue and opening a draft pull request |
| Stale claim | A claim whose holder has stopped writing for longer than the protocol allows, usually because it crashed or ran out of quota | claimed_at older than STALE_AFTER with no write to the .notes file since |
| Enforced rule | A rule the medium itself guarantees, whether or not the agents cooperate | One os.rename() on one disk succeeds and the other fails; GitHub merges a pull request once |
| Advisory rule | A rule that holds only because both agents chose to follow it | The staleness timeout; “only the holder writes the notes”; what to do with a conflicted copy |
| Conflicted copy | The file a sync service creates when two machines changed the same path before either saw the other’s change | task-7 (conflicted copy).md beside the winner in a Dropbox folder |
| Trace line | One JSON object per action, naming who acted, what they did, and the rule that allowed it, with no prompt text | A line in .ai/trace.jsonl with "rule": "handoff-protocol#10" |
Part I: Start, Stop, Restart
1. Three Moments Every Session Has
A handoff protocol is mostly about two agents, but it starts with one. Every session starts, every session stops, and sooner or later a session restarts as a stranger to its own work. The Governing Coding Agents tutorial and Part IV of the Agents That Talk session give each moment a rule. You will use the files they name here:
- On start, read in a fixed order before doing anything else:
START_HERE.md,CHARTER.md,docs/ROADMAP.md,.ai/CURRENT_TASK.md,.ai/SESSION.md(at least the most recent entry), and the most recent commit. Then state the mission, the active task, and the Next Safe Action in your own words. Do not act until you have. - On stop, for any reason, including finishing normally, append a dated entry to
.ai/SESSION.mdthat ends with a Next Safe Action, and update.ai/CURRENT_TASK.md. Annotate superseded guidance in place, and never delete it. - On restart, run the start rule, and then re-run every command in the Reality Check table of
CURRENT_TASK.mdbefore relying on it. That table lists what is not done. Each row gives the command or test that proves it.
Handing work to a second agent adds one more thing to the stop rule: the claim. The rest of this page is about that addition.
Questions to Work Through
-
Why does the stop rule apply when a session finishes normally, and not only when it is interrupted? Think of a Friday session that finishes cleanly and a Monday session run by a different tool.
-
A restarted agent reads “12 of 14 tests pass” in
CURRENT_TASK.md. What should it do before it believes that line, and what would it be risking if it skipped that step?
Part II: Claims Through a Durable Medium
2. Four Rules a Claim Protocol Needs
The Agent Skills and Plugins tutorial lists four things a handoff SKILL.md must specify. Here they are with the folder from the Agents That Talk session beside them:
handoff/
|-- inbox/ # unclaimed tasks. Whoever wrote one may not work it.
|-- claimed/
| |-- task-7.md # the task, moved here by the agent that owns it
| |-- task-7.md.claim # who owns it and since when: claimed_by, claimed_at
| `-- task-7.md.notes # the owner's plan and questions, appended, never rewritten
`-- done/
|-- task-7.md
`-- task-7.result.md # what happened, what did not, and the Next Safe Action
- How to claim. Move the task from
inbox/toclaimed/, then write the.claimfile withclaimed_byandclaimed_at. The test of a claim is whether the other agent can see it through the medium alone, with no message from you. - What a second agent does on seeing a claim. Skip it, wait, or take it. Your protocol has to pick one.
- When a claim is stale, and who may break it. An agent that claims an item and dies leaves it claimed forever unless some rule says otherwise. The rule needs a number (how long with no write) and a name (which agents may take over). An agent that breaks a claim records whose claim it took.
- What done looks like. Anything in
done/with a.result.mdbeside it is finished. Anything still inclaimed/with a stale claim is abandoned. A fresh claim is work in progress, and a file ininbox/has not been started.
Write each rule as a path and a condition. “Agents should coordinate” is not a rule anyone can check.
3. Enforced Rules and Advisory Rules
Some of those rules hold no matter what the agents do. Others hold only because both agents agreed to them. The difference matters most when the second agent runs a different model, because a different model may read your advisory rules differently.
| Rule | Folder on one disk | Folder synced across two machines | GitHub |
|---|---|---|---|
| Only one agent wins a claim | Enforced: os.rename() is atomic, so one rename succeeds and the other gets “file not found” |
Advisory: each machine’s rename succeeds on its own disk, and the sync service later leaves a conflicted copy | Advisory: GitHub allows several assignees and does not stop a second agent from opening its own pull request |
| Work lands once | Partly: finish() uses a rename, which fails if the task has already left claimed/, but nothing checks that the finisher still holds the claim |
Advisory | Enforced: a pull request is merged once, and a draft pull request cannot be merged until it is marked ready |
| A stale claim may be broken after N minutes | Advisory | Advisory | Advisory |
| Only the holder writes the notes | Advisory | Advisory | Advisory: anyone with access can comment on the issue or push to the branch |
Two habits follow from the table. First, put your most important rule where the medium enforces it. That is why the claim is a rename and not “check whether the file is there, then copy it.” Second, say in your protocol what an agent does when an advisory rule has clearly been broken. For a conflicted copy, stop and ask is the safe default.
One more point from the Agent Skills tutorial: every clause in a SKILL.md is a request to the model, not a rule the harness enforces. The medium enforces the rename and the merge. A hook (such as a Stop hook that checks whether SESSION.md changed) can verify a few more rules. Everything else is advisory.
4. One Trace Line per Action
Every claim, skip, wait, takeover, and finish writes one JSON line to .ai/trace.jsonl. Every line carries a rule field that names the rule in your skill that allowed the action. This is the same habit as the rule field in Model 4 of the Agents That Talk session. A trace without the rule tells you what happened. A trace with it tells you what to change. Rule 4 of the skeleton in Part IV logs each loop phase. The handoff actions add their own lines to the same file, with the same trace_id habit.
Store identifiers and measurements, never content. The trace gets the task name, the agent name, the time, the rule, and numbers such as how long the claim had been idle. It does not get the prompt, the task text, or the notes. The notes live in their own file, which the medium already keeps.
Questions to Work Through
-
The
.claimfile is written after the rename succeeds. What does a third agent see if it looks atclaimed/in the moment between the two steps? Would the other order be better or worse? -
In the table above, “work lands once” is advisory for a synced folder. Two machines share
handoff/through a sync service. Describe a sequence of events in which two results for the same task end up indone/.
Part III: Practice the Protocol in Isolation
Practice this on a toy problem before you put it in a project, so that a broken claim costs you nothing. Pick one path. Both practice the same four rules, and both end with a trace you can read.
5. The Code Path: A Claim Race Over a Folder
The three cells below need only python3 and the standard library. Save each one as its own file in an empty folder and run it with python3 <file>.py. The first cell creates a handoff-demo/ folder in the folder you run it from. The third cell reads that folder, so run them from the same place. The “agents” here are one set of Python functions, called with two agent names, standing in for two agent sessions. The protocol does not care what is on the other side of the folder.
Code Cell: The Protocol, One Step at a Time
This is the claim race from Model 3 of the Agents That Talk session, with two additions: a .notes file whose writes count as a sign of life, and a trace line for every action. Read claim() first. The whole claim rule rests on one fact: a rename cannot half-happen.
import os, json, time, shutil
ROOT = "handoff-demo"
shutil.rmtree(ROOT, ignore_errors=True) # fresh folder for the demo
INBOX, CLAIMED, DONE = (os.path.join(ROOT, "handoff", d)
for d in ("inbox", "claimed", "done"))
TRACE = os.path.join(ROOT, ".ai", "trace.jsonl")
for d in (INBOX, CLAIMED, DONE, os.path.dirname(TRACE)):
os.makedirs(d, exist_ok=True)
STALE_AFTER = 300 # seconds with no write before a claim may be broken
TRACE_ID = "t-task7"
def log_action(agent, action, task, rule, **fields):
"""One JSON line per action. Names and numbers only, never prompt text."""
record = {"ts": round(time.time(), 3), "trace_id": TRACE_ID, "agent": agent,
"action": action, "task": task, "rule": rule}
record.update(fields)
with open(TRACE, "a") as f:
f.write(json.dumps(record) + "\n")
def post_task(name, text):
with open(os.path.join(INBOX, name), "w") as f:
f.write(text)
def claim(agent, name):
src, dst = os.path.join(INBOX, name), os.path.join(CLAIMED, name)
try:
os.rename(src, dst) # atomic on one disk: one caller wins
except FileNotFoundError:
log_action(agent, "skip", name, "handoff-protocol#9")
return False # someone else already moved it
with open(dst + ".claim", "w") as f:
json.dump({"claimed_by": agent, "claimed_at": time.time()}, f)
log_action(agent, "claim", name, "handoff-protocol#8")
return True
def note(agent, name, text):
"""Append to the notes file. Each write also counts as a sign of life."""
with open(os.path.join(CLAIMED, name + ".notes"), "a") as f:
f.write(f"[{agent}] {text}\n")
log_action(agent, "note", name, "handoff-protocol#8")
def last_write(name):
with open(os.path.join(CLAIMED, name + ".claim")) as f:
c = json.load(f)
notes = os.path.join(CLAIMED, name + ".notes")
newest = c["claimed_at"]
if os.path.exists(notes):
newest = max(newest, os.path.getmtime(notes))
return c, newest
def take_if_stale(agent, name):
c, newest = last_write(name)
idle = time.time() - newest
if c["claimed_by"] == agent or idle < STALE_AFTER:
log_action(agent, "wait", name, "handoff-protocol#10", idle_s=int(idle))
return False # the holder may still be working
with open(os.path.join(CLAIMED, name + ".claim"), "w") as f:
json.dump({"claimed_by": agent, "claimed_at": time.time(),
"taken_from": c["claimed_by"]}, f)
log_action(agent, "break-claim", name, "handoff-protocol#10",
idle_s=int(idle), taken_from=c["claimed_by"])
return True
def finish(agent, name, result):
os.rename(os.path.join(CLAIMED, name), os.path.join(DONE, name))
os.remove(os.path.join(CLAIMED, name + ".claim"))
notes = os.path.join(CLAIMED, name + ".notes")
if os.path.exists(notes):
os.rename(notes, os.path.join(DONE, name + ".notes"))
with open(os.path.join(DONE, name.replace(".md", ".result.md")), "w") as f:
f.write(f"finished_by: {agent}\n\n{result}\n")
log_action(agent, "finish", name, "handoff-protocol#11")
def show():
for d in (INBOX, CLAIMED, DONE):
print(f" {os.path.basename(d)}/: {sorted(os.listdir(d))}")
post_task("task-7.md", "Summarize the three failure modes of message passing.")
print("A claims:", claim("agent-A", "task-7.md"))
print("B claims:", claim("agent-B", "task-7.md"))
note("agent-A", "task-7.md", "Plan: read Part II, list the modes, cite the deck.")
print("B takes over too early:", take_if_stale("agent-B", "task-7.md"))
show()
# Agent A dies here. Move the clock back on both of its writes.
path = os.path.join(CLAIMED, "task-7.md.claim")
with open(path) as f:
c = json.load(f)
c["claimed_at"] -= STALE_AFTER + 1
with open(path, "w") as f:
json.dump(c, f)
old = time.time() - STALE_AFTER - 1
os.utime(os.path.join(CLAIMED, "task-7.md.notes"), (old, old))
print("B takes the stale claim:", take_if_stale("agent-B", "task-7.md"))
finish("agent-B", "task-7.md", "Lost message, crashed receiver, unparseable schema.")
show()
It prints:
A claims: True
B claims: False
B takes over too early: False
inbox/: []
claimed/: ['task-7.md', 'task-7.md.claim', 'task-7.md.notes']
done/: []
B takes the stale claim: True
inbox/: []
claimed/: []
done/: ['task-7.md', 'task-7.md.notes', 'task-7.result.md']
Now open handoff-demo/.ai/trace.jsonl. There are six lines, one per action, and each one names a rule. A’s claim and note fall under rule 8, B’s skip under rule 9, B’s early wait and its later takeover under rule 10 (with idle_s and taken_from), and B’s finish under rule 11. The task text and the note text are not in it.
This cell runs the agents one after the other, so it cannot show you a real race. The next cell does.
Code Cell: Two Agents at the Same Moment
Here, the code releases two threads at the same instant, a hundred times, and both threads try to claim the same file. The first version claims with a rename. The second checks whether the file is there and then copies it, which is two steps.
import os, shutil, tempfile, threading, time
def rename_claim(src, dst):
try:
os.rename(src, dst) # one step: it happens or it does not
return True
except FileNotFoundError:
return False
def check_then_act_claim(src, dst):
if not os.path.exists(src): # step 1: check
return False
text = open(src).read()
time.sleep(0.01) # anything can happen between the steps
open(dst, "w").write(text) # step 2: act
try:
os.remove(src)
except FileNotFoundError:
pass # the other agent already removed it
return True
def race(claim_fn, rounds=100):
double = 0
for _ in range(rounds):
root = tempfile.mkdtemp()
src = os.path.join(root, "task-7.md")
open(src, "w").write("one task")
start = threading.Barrier(2) # release both agents at the same moment
winners = []
def agent(who):
start.wait()
if claim_fn(src, os.path.join(root, f"claimed-by-{who}.md")):
winners.append(who)
threads = [threading.Thread(target=agent, args=(w,)) for w in "AB"]
for t in threads:
t.start()
for t in threads:
t.join()
if len(winners) > 1:
double += 1
shutil.rmtree(root)
return double
print("rename: rounds with two winners =", race(rename_claim))
print("check then act: rounds with two winners =", race(check_then_act_claim))
The first line should print 0 every time you run it. The second line prints how many of the hundred rounds ended with both agents believing they owned the task. Run it a few times and write down what you see. The second number can change from run to run, but the first should not.
Code Cell: Auditing the Trace
The checklist in Part IV asks two things of every trace: each action names a rule, and no prompt text or other content is stored. A few lines of Python can check both. Run this after the first cell.
import json
from collections import Counter
TRACE = "handoff-demo/.ai/trace.jsonl"
FORBIDDEN = {"prompt", "text", "content", "messages"} # content, not measurements
actions, problems = Counter(), []
with open(TRACE) as f:
for n, line in enumerate(f, start=1):
rec = json.loads(line)
actions[(rec["agent"], rec["action"])] += 1
if not rec.get("rule"):
problems.append(f"line {n}: {rec['action']} names no rule")
leaked = FORBIDDEN & rec.keys()
if leaked:
problems.append(f"line {n}: stores {sorted(leaked)}")
for (agent, action), count in sorted(actions.items()):
print(f"{agent:8} {action:12} x{count}")
print("problems:", problems or "none")
On the trace from the first cell, it prints one row for each of the six actions and ends with problems: none. Now change note() in the first cell so its log_action call also passes text=text, rerun both cells, and confirm that the audit catches it.
Questions to Work Through
-
Predict before you change anything: set
STALE_AFTER = 0in the first cell and rerun it. What does “B takes over too early” print now, and what does that tell you about choosing the timeout for a slow but healthy agent? -
In the second cell, why does the
time.sleep(0.01)make the check-then-act bug show up so often? If you deleted the sleep, would the code be correct? -
Agent A was slow, not dead. It wakes up after B took the claim and calls
finish("agent-A", ...). Trace what happens todone/and to the trace. Which rule needs another clause, and what check would you add tofinish()? -
take_if_stale()reads the.claimfile and then rewrites it, which is check-then-act again. What happens if two agents both decide the claim is stale at the same moment? How could a rename make breaking a claim safe in the same way it makes claiming safe?
6. The No-Code Path: The Same Protocol by Hand
You can run the whole protocol without writing a line of code. Use two agent sessions that do not share a context window, two people, or yourself playing both roles in two browser windows. Choose the GitHub version or the folder version.
With a GitHub Issue
Make a throwaway practice repository for this, not your project repository.
- Post the task. Open an issue with the task and its acceptance criteria. Create an
in-progresslabel. - Claim it (rule 8). Agent A assigns itself, adds
in-progress, and comments “Claiming this. Plan: … (rule 8)”. Then it opens a draft pull request whose description saysCloses #<issue number>, so the claim is also visible from the pull request list. - See the claim (rule 9). Agent B opens the issue, sees the assignee, the label, and the linked draft pull request, and comments “Skipping, claimed by A (rule 9).”
- Break a stale claim (rule 10). Decide on a number before you start, such as two hours with no commit and no comment on the draft pull request. Once that has passed, B comments “Claim from A is stale: last activity at [time] (rule 10)”, reassigns the issue to itself, and either continues A’s branch or opens its own draft pull request and closes A’s.
- Finish (rule 11). The holder marks the pull request ready for review, someone merges it, and the issue closes against the merge commit. Remove
in-progress.
Finished work is a closed issue with a merged pull request. Abandoned work is either an open issue whose assignee has gone quiet, or an issue closed with no merged pull request (GitHub’s “Close as not planned” option records this). The issue’s timeline already records who changed each label and assignee and when, so GitHub writes part of your trace for you. It does not record the rule, which is why each comment ends with one.
With a Shared Folder, Moved by Hand
- Make
handoff/inbox/,handoff/claimed/, andhandoff/done/in a folder both “agents” can reach. Puttask-7.mdininbox/. - Claim it (rule 8). Drag
task-7.mdfrominbox/intoclaimed/in your file manager. Then createtask-7.md.claimbeside it with two lines,claimed_by: Aandclaimed_at: <date and time>. - Race it. Have both “agents” count down and drag the same file at the same moment. On one disk, only one move can succeed, because the file is gone by the time the second one tries. If the folder is synced between two machines, look for a conflicted copy a minute later.
- Break and finish (rules 10 and 11) the same way the code does. To break the claim, edit the
.claimfile to name the new holder and add ataken_from:line. To finish, move the task intodone/and writetask-7.result.mdwithfinished_by:and a Next Safe Action. - Trace it. For every move or edit, add one line to
.ai/trace.jsonlby hand, such as{"ts": "2026-11-02 14:05", "agent": "B", "action": "break-claim", "task": "task-7.md", "rule": "handoff-protocol#10"}. Leave out the task text and the notes.
The folder version teaches one thing the code does not. Dragging a file between folders on the same drive is a rename. Dragging it to a different drive is a copy followed by a delete, which is two steps. Keep all three folders on one drive.
Questions to Work Through
-
In the GitHub version, both agents assign themselves within the same minute. What does the issue show afterward? Which of rules 8 through 11 did GitHub enforce, and which did it only display?
-
In the folder version, which of your hand-written trace lines could you check against the medium itself (the files in each folder, the sync service’s history), and which could only be taken on trust?
Part IV: A Handoff Skill for Your Project
7. Choose the Repository and the Medium
Now take what you practiced into a real repository. Choose one project repository of your own. Your team’s final project repository is one option. It is the natural choice if more than one agent works in it, because the Responsible AI Report asks for traces of agent actions and handoffs. Your cs357-work repository, or any other repository you will keep working in, also works.
Then choose the medium the agents will hand work through: GitHub issues and pull requests, a handoff/ folder inside the repository, or an Obsidian vault folder (the Agent Skills tutorial lists all three). If your repository does not have START_HERE.md, CHARTER.md, or the .ai/ files yet, the agent templates have a starting copy of each. That step is optional. The skill works as long as rule 1 names files that exist.
8. Write the SKILL.md
Put the skill at .agents/skills/handoff-protocol/SKILL.md in that repository. The directory name must match the name in the front matter. Both opencode and pi look in .agents/skills/. Start from the skeleton in Model 5 of the Agents That Talk session:
---
name: handoff-protocol
description: >
Follow at the start, stop, and restart of every session, and whenever
work is handed to another agent through the repository or the shared
folder. Load before doing anything else in a session.
---
## On start
1. Read, in order: START_HERE.md, CHARTER.md, .ai/CURRENT_TASK.md,
.ai/SESSION.md (last three entries), the most recent commit.
2. State the mission, the active task, and the Next Safe Action.
Do not act until they are stated.
3. Re-run every command in the CURRENT_TASK.md Reality Check table.
## While working
4. Append one JSON line to .ai/trace.jsonl per loop phase: trace_id,
step, phase, and on every act line the rule that allowed the action.
Never write prompt text or retrieved content into the trace.
## On stop (any reason, including finishing)
5. Append a dated SESSION.md entry: Scope, In Progress, Completed
(with proving commands), Runtime Facts, Validation, Next Safe Action.
6. Update the Reality Check in CURRENT_TASK.md. Never overwrite an entry.
7. Commit one logical change. Leave failing work uncommitted and named.
## Handing to another agent
8. Claim: [the path and field, e.g., rename into handoff/claimed/ and
write the .claim file, or assign yourself the issue and open a draft PR].
9. On a claimed item: [skip, wait N minutes, or take; say which].
10. Stale claim: [claimed_at older than N with no write since; name who
may break it; say what to do with a conflicted copy].
11. Done: [move to handoff/done/ with a .result.md, or merge the PR
and close the issue naming the commit].
## Limits
12. Watch context, quota, and time. When any is near, stop new work and
run rules 5 through 7 before failure.
Fill in rules 8 through 11 for the medium you chose, using what you saw in Part III. Two more edits are worth making. If your repository has a docs/ROADMAP.md, add it to rule 1 after CHARTER.md, since the kickoff template reads it there. And give each rule an identifier your trace lines can cite, such as handoff-protocol#10, the way the code path did.
9. Check It
Read your skill against this checklist from the Agents That Talk session. Every row must be true of your protocol, and every row must cite the rule number that makes it true.
| Check | Rule that makes it true |
|---|---|
| A fresh agent can state mission, task, and Next Safe Action from files alone | |
| Every stop, including a normal finish, writes the handoff state | |
| Every action in the trace names the rule that allowed it | |
| No prompt text or retrieved content is stored in the trace | |
| A claim is visible to the other agent through the medium alone | |
| A stale claim can be broken, and the protocol says by whom and when | |
| Finished work is distinguishable from abandoned work in the medium | |
| Every not-done row in the Reality Check cites a command or artifact |
Questions to Work Through
-
Mark each of your rules 8 through 11 as enforced by your medium or advisory. Which one is the first to break if the second agent runs a different model?
-
Your skill is text the model reads, so every clause is a request. Pick the one clause you most need to hold every time. What would a program look at to decide whether it held, and could a
Stophook check it?
Exercises
These are optional, and both use the repository and skill from Part IV. Each one ends with a check you can apply yourself.
-
The conflict.
What to do: Point two sessions that do not share a context window at the same unclaimed item in your project’s medium, at the same time. Report what happened.
Starter hint: There is no correct outcome. Either your claim held, and you can say what mechanism held it, or you got double work or a lost write. The second is a passing result if you diagnose it: show the evidence, name the rule that would have prevented it, and say whether that rule is enforced or advisory in your medium.
You’ve succeeded when: You can say which of your rules the medium enforces and which hold only because both agents chose to follow them.
-
Hand it to a stranger.
What to do: Commit your
SKILL.md, start a fresh session in that repository, and ask it to pick up the next unclaimed item. Record every question it asks before it claims anything.Starter hint: Each question is a missing sentence in your skill or in one of the files rule 1 reads. Add the sentence. Do not write a longer prompt.
You’ve succeeded when: A second fresh session claims, works, and stops cleanly without asking anything, and its trace passes the audit from the third code cell (or the same two checks done by eye).
Reflection Prompt
Personal: Think of a time someone said “I’ve got this section” in a group project and then went quiet. How long did you wait before you took it over, who decided, and did you tell them? What would have made that easier to settle?
Technical: The trace in this tutorial is written by the agents about themselves. Which lines would you trust only if something else produced them, such as the filesystem, a wrapper around the tool call, or GitHub’s own timeline? What would it cost you to get them that way?
Societal: When a stale claim or a race between two agents produces a wrong result that reaches a real person, the fault belongs to the interaction rather than to either agent alone. Who should be responsible: the people who wrote the protocol, the people who ran the agents, or someone else?
Further Reading
- This course: Agents That Talk, Observability, and Handoff Protocols, Parts II and IV, where the claim race, the
SKILL.mdskeleton, and the checklist come from. - This course: Agent Skills and Plugins, for where skills live on disk, why every clause is a request, and the claim-protocol section this page practices.
- This course: Governing Coding Agents, for the read-order funnel, the
.ai/directory, and the mid-session swap. - This course: Memory and the Small Context Window Principle, for why state has to leave the context window in the first place.
- This course: Agent Observability and Tracing, for logs, traces, and metrics, and for what not to store in a trace.
- This course: the agent templates, including
AGENT_HANDOFF_KICKOFF.mdandSESSION.md, and the Final Project Responsible AI Report, which asks for traces of agent actions and handoffs.