Free agent compute fails at the shift change, not the prompt.
A team loses hours when one engineer leaves a half-run loop.
The next person inherits a dirty desk and no owner.
Shared free models make that failure cheaper and more frequent.
Few people close a run with a clean freeze.
The missing artifact is a one-page wiki handoff card.
This playbook treats agent coding as a staffed shift.
It names roles, freeze rules, and a paste-ready run.
The method stays useful without any particular vendor.
Why shift change eats the work
Agent sessions look private on a laptop screen.
They are not private on a shared server.
A free server is a shared bench, not a drawer.
Tools, branches, and logs pile up like unwashed glassware.
The first engineer chases a failing test with a long prompt.
Then a meeting starts and the loop stays hot.
The second engineer cannot tell intent from leftover noise.
Vibe sessions skip the freeze and call it flow.
Engineering parks the bench before the next shift arrives.
A handoff card is the ice tray for that melt.
It records what froze and what still drips.
It also names who owns the next pour.
Without that freeze, two prompts fight over one test file.
The later prompt wins on disk and loses in git history.
The team then debugs authorship instead of the timeout.
Roles that survive a messy afternoon
Four roles cover a small team without extra ceremony.
The Shift Owner starts the run and later parks it.
The Desk Steward keeps the shared server from becoming a junk drawer.
The Reviewer never runs the agent on the same change.
That split keeps the patch from grading its own homework.
The Next Runner claims a frozen card before touching files.
One person may hold two roles on a tiny squad.
The Reviewer still stays off the generating keyboard.
That single constraint prevents most overnight prompt drift.
The four roles belong on the wiki, not in chat.
Chat threads rot by morning and lose the owner.
Wiki pages stay at a known URL for the desk.
echo "shift cards: https://wiki.example/agent-shift-card" | sudo tee /etc/motd.d/agent-desk
The MOTD is a door sign, not a process manager.
People still miss the MOTD on a busy login.
The lock file remains the backup sign on disk.
Both exist because humans forget trays on a busy bench.
A labeled tray beats a clever oral briefing every time.
The rest of this playbook exists to make that label machine-checkable.
The one-page card, ready for a wiki
The steward pastes the block below into the team wiki.
The desk keeps one live card per active branch.
The owner closes the card when the branch merges or dies.
# Agent Shift Card
Status: PARKED | CLAIMED | CLOSED
Branch:
Worktree:
Ticket:
Shift Owner:
Desk Steward:
Reviewer:
Next Runner:
Started:
Parked:
Claimed:
## Intent (one sentence)
...
## Freeze
Last command:
Last passing test:
Last failing test:
Files the agent may touch:
Files the agent must not touch:
## Residue
Uncommitted paths:
Secrets risk (none/low/stop):
Server process ids:
Scratch dirs:
## Resume recipe
Step A:
Step B:
Step C:
## Stop line
The Next Runner stops if ...
The card is dull on purpose and that is fine.
Dull cards survive copy-paste better than clever ones.
A wiki page beats a chat scroll when the pager rings.
Status values are a traffic light, not a novel.
PARKED means the tray is labeled and idle.
CLAIMED means one named runner holds the lock.
CLOSED means merge or abandon, with residue wiped.
A closed card with live pids is a failed close.
The steward treats that failure as a desk defect.
A file the desk can lint
Talk is cheap when the desk changes hands.
A sibling file can fail a hook before park.
Keep a sibling shift-card.yml in the worktree.
The steward refuses a park when required keys are empty.
Empty keys are how folklore replaces a freeze.
Folklore is how a retry helper lands in the ledger path.
status: parked
branch: fix/retry-timeout
worktree: /srv/agent-desk/fix-retry-timeout
ticket: PAY-1842
shift_owner: alex
desk_steward: rina
reviewer: sam
next_runner: ""
started: "2026-09-16T14:10:00Z"
parked: "2026-09-16T16:40:00Z"
claimed: ""
intent: "Retry timeout should fail closed after three attempts."
last_command: "pytest tests/test_retry.py -q"
last_pass: "tests/test_retry.py::test_zero_delay"
last_fail: "tests/test_retry.py::test_fail_closed"
allow_paths:
- "src/retry.py"
- "tests/test_retry.py"
deny_paths:
- "src/payments/ledger.py"
- ".env"
residue_paths:
- "src/retry.py"
- "tests/test_retry.py"
secrets_risk: none
server_pids: []
scratch_dirs:
- "/tmp/agent-fix-retry-timeout"
stop_line: "Stop if ledger.py appears in git status."
Empty next_runner is valid at park time.
Empty next_runner is not valid at claim time.
The steward script encodes that difference in two modes.
Ticket keys bind the tray to a real queue.
Without a ticket, parked work becomes folklore by morning.
The yaml file exists so the wiki card can be wrong and still fail.
Commands that make the freeze real
The Shift Owner parks with a short shell sequence.
The sequence is boring and that is the point.
Every step leaves a trace the Next Runner can read.
#!/usr/bin/env bash
set -euo pipefail
CARD=shift-card.yml
BRANCH=$(yq -r .branch "$CARD")
mkdir -p .shift
git status --porcelain > .shift/residue.txt
git diff --stat > .shift/diffstat.txt
git rev-parse HEAD > .shift/head.txt
date -u +%Y-%m-%dT%H:%M:%SZ > .shift/parked_at.txt
if grep -E '\.env$|id_rsa|credentials' .shift/residue.txt; then
echo "refuse park: secret-shaped path in residue"
exit 2
fi
echo "parked ${BRANCH} at $(cat .shift/head.txt)"
The stash line stays optional on a dedicated worktree.
A dedicated worktree is the safer analogy here.
Think of it as a labeled tray, not a shared cutting board.
#!/usr/bin/env bash
set -euo pipefail
# proposal: create a labeled worktree before the first prompt
CARD=shift-card.yml
BRANCH=$(yq -r .branch "$CARD")
ROOT=/srv/agent-desk
git worktree add "${ROOT}/${BRANCH}" -b "${BRANCH}"
yq -i ".worktree = \"${ROOT}/${BRANCH}\"" "$CARD"
Claim is the reverse motion, with a lock.
Two named runners cannot hold one fridge sign.
The lock file encodes that rule without a new service.
#!/usr/bin/env bash
set -euo pipefail
CARD=shift-card.yml
LOCK=.shift/CLAIMED_BY
if [[ -f "$LOCK" ]]; then
echo "desk already claimed by $(cat "$LOCK")"
exit 3
fi
whoami > "$LOCK"
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
yq -i ".status = \"claimed\" | .next_runner = \"$(whoami)\" | .claimed = \"${NOW}\"" "$CARD"
echo "claimed $(yq -r .branch "$CARD")"
Two engineers claiming the same tray rewrite tests twice.
The lock file is a paper sign on the lab fridge.
The fridge sign does not need to be a consensus algorithm.
Release the lock only in close, never in panic.
Panic closes laptops. It does not wipe scratch dirs.
The close script is the only legal wipe path.
#!/usr/bin/env bash
set -euo pipefail
CARD=shift-card.yml
rm -f .shift/CLAIMED_BY
yq -i '.status = "closed" | .next_runner = ""' "$CARD"
rm -rf "$(yq -r '.scratch_dirs[0]' "$CARD")"
echo "closed $(yq -r .branch "$CARD")"
Scratch directories die with the closed card.
Orphan tmp folders are how free disks fill overnight.
The steward greps /tmp during Friday desk hours.
find /tmp -maxdepth 1 -name 'agent-*' -printf '%p %u %TY-%Tm-%Td\n'
An orphan directory with yesterday's date is unpaid residue.
Unpaid residue is still the last owner's problem.
The wiki card stays OPEN until that path is gone.
A tiny linter for the card
The following script is a proposal, not a production service.
Teams should run it locally before calling park complete.
It checks presence of keys, not wisdom of prompts.
#!/usr/bin/env python3
"""Lint a shift-card.yml before park or claim. Proposal only."""
from pathlib import Path
import sys
import yaml
REQUIRED = [
"status", "branch", "worktree", "ticket",
"shift_owner", "desk_steward", "reviewer",
"intent", "last_command", "stop_line",
"allow_paths", "deny_paths", "secrets_risk",
]
def main(path: str, mode: str) -> int:
data = yaml.safe_load(Path(path).read_text())
missing = [k for k in REQUIRED if not data.get(k)]
if missing:
print("missing:", ", ".join(missing))
return 1
if data["secrets_risk"] not in {"none", "low", "stop"}:
print("secrets_risk must be none, low, or stop")
return 1
if data["secrets_risk"] == "stop":
print("refuse: secrets_risk is stop")
return 2
if mode == "park" and data["status"] != "parked":
print("park mode expects status parked")
return 1
if mode == "claim":
if not data.get("next_runner"):
print("claim requires next_runner")
return 1
if data["shift_owner"] == data["reviewer"]:
print("reviewer must differ from shift_owner")
return 1
deny = set(map(str, data["deny_paths"]))
residue = set(map(str, data.get("residue_paths") or []))
overlap = deny & residue
if overlap:
print("residue touches deny_paths:", ", ".join(sorted(overlap)))
return 2
print("ok")
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1], sys.argv[2]))
The steward runs it like this before any claim.
Park mode and claim mode share one file on purpose.
Two files would drift, like two unlabeled trays.
python3 lint_shift_card.py shift-card.yml park
python3 lint_shift_card.py shift-card.yml claim
Teams may hook the park lint to the repository for teeth.
A hook is optional. A failed park should still be loud.
Loud beats polite when ledger files enter residue.
#!/usr/bin/env bash
# .git/hooks/pre-push — proposal only, not a default
set -euo pipefail
if [[ -f shift-card.yml ]]; then
python3 lint_shift_card.py shift-card.yml park
fi
The deny-path check is the sharp edge.
Ledger code should not appear because a retry prompt wandered.
If it does, the card fails closed like a circuit breaker.
Where free shared compute fits
A team that shares free model access needs this freeze more.
Idle prompts feel costless, so people leave loops running.
A free server option turns that habit into a crowded bench.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode is one source of free model access for a shared desk.
It also offers a free server option for that desk.
The handoff card does not depend on that vendor.
Any shared agent host with a worktree can use the same freeze.
The steward still maps one worktree to one card.
The steward still kills leftover processes at park.
Product names do not replace pid files on the desk.
# proposal: list leftover python agent processes on the desk
ps -u "$(whoami)" -o pid,etime,cmd | awk '/pytest|python/ {print}'
If a process outlives the card, the desk is lying.
Treat that as a failed park, not a style note.
Kill the pid, then rewrite residue, then park again.
A one-page run for the wiki
The steward puts this run under the card template.
The team reads it top to bottom during a calm park.
Incident time is the wrong time to invent a freeze.
AGENT SHIFT RUN (park -> claim -> resume -> close)
1. Shift Owner writes intent in one sentence.
2. Shift Owner fills allow_paths and deny_paths before the first prompt.
3. Desk Steward confirms the worktree is empty of other cards.
4. Shift Owner runs the agent only inside allow_paths.
5. On pause, Shift Owner dumps git status into .shift/residue.txt.
6. Shift Owner runs lint_shift_card.py in park mode.
7. Desk Steward greps residue for secret-shaped names.
8. If secrets_risk is stop, the desk stays frozen and paging starts.
9. Next Runner claims with the lock file, never by chat.
10. Next Runner replays last_command before any new prompt.
11. Reviewer reads the diff with the card beside it.
12. Close the card on merge, or on explicit abandon.
Twelve steps look long on a wiki page.
They are not long once the files exist.
Most take under a minute during a calm park.
The expensive step is replaying the last command.
That replay is the whole point of the freeze.
A card that cannot replay is a postcard, not a handoff.
The Next Runner then has evidence, not folklore.
Replay belongs in the resume recipe as Step A.
Step B is reading deny_paths out loud together.
Step C is one new prompt, or none.
None is a legal resume when the failing test already speaks.
A new prompt without replay is just another vibe session.
What this does not fix
The card does not make weak tests strong.
It does not rank models or promise uptime.
It does not replace code review or a merge check.
Free shared compute can vanish or throttle without notice.
Teams should not park customer secrets on an uncontrolled desk.
A free server is not a production runner.
The linter only sees keys the owner typed.
It cannot see a prompt that asked for a production dump.
Human review still sits on the deny list.
A parked card also does not freeze the remote trunk.
Someone else can still merge a colliding change.
The Next Runner rebases after replay, never before it.
Who should skip this playbook
Solo developers with one laptop gain little from this SOP.
The lock file and steward role are overhead on a single desk.
A personal branch with a good commit message is enough.
Teams handling regulated data should not use shared free servers.
They need a private runner and a real secret store.
This SOP assumes a low-risk scratch bench only.
Incident commanders should not run agents from a parked card.
Incidents need a known binary, not a half-edited helper.
Close the card or ignore it until the incident ends.
Close the tray, then walk away
Shift change is where agent work leaks between owners.
A wiki card, a yaml file, and a lock keep the tray labeled.
The team then resumes a freeze, not a vibe.
The team pastes the card into the wiki before the next shared desk hour.
Top comments (0)