Unowned agent hours turn free compute into orphan diffs. A posted floor roster stops that quiet drift. The roster names a captain and a path fence before the run.
Night kitchens fail without a chef de partie. Shared agent hours fail in that same way. Tokens move, files change, and nobody owns the burn.
A floor captain holds the shift, not the model. The captain posts the roster and opens the fence. Named seats support that watch and never replace it.
The prompt clerk drafts the task brief for the captain. The clerk never merges those agent edits. The diff steward reads every path the agent touched.
The freeze officer can halt the run when the fence breaks. These four seats still fit a small team. One person may hold two seats on a quiet night.
Two seats must never collapse into one pair of hands. The captain and freeze officer must stay distinct. That split is lockout-tagout for agent work.
The captain starts the machine for the window. The freeze officer can stop that machine. The tag lives in the wiki, not in chat scatter.
Paste the card below into the team wiki. Treat the card as a live shift object. Empty seats mean the window stays closed.
# Floor Roster — Agent Hours
Shift ID:
Window (UTC):
Repo / branch:
Floor captain:
Prompt clerk:
Diff steward:
Freeze officer:
Allowed roots:
Forbidden roots:
Status: closed | open | frozen
Kill command:
Sign-off time (UTC):
The card stays short on purpose. Long charters hide missing names in plain sight. A missing freeze officer is a closed kitchen.
Pair the card with a fence file in git. The fence is the only map the agent may walk. Paths outside that map must fail the checker.
# .agent/fence.txt — proposed local fence, not a vendor format
# allowed prefixes, one per line
apps/web/src/
docs/runbooks/
# forbidden prefixes after a blank line
.github/
infra/
secrets/
Keep the roster beside that fence as structured data. The checker should read files, not a chat pin. The sample below is a proposed local gate.
# .agent/roster.yaml — proposed shift card
shift_id: "2026-09-18-eu-late"
window_utc: "18:00/22:00"
floor_captain: "alex"
prompt_clerk: "sam"
diff_steward: "riley"
freeze_officer: "jordan"
status: "open"
A proposed checker then loads the roster and the fence. The script is a local gate, not a hosted service. Run it on the shift branch before any agent loop starts.
# check_roster.py — proposed local gate, unexecuted here
from pathlib import Path
import sys
import yaml
ROSTER = Path(".agent/roster.yaml")
FENCE = Path(".agent/fence.txt")
CHANGED = Path(".agent/changed.txt")
FROZEN = Path(".agent/FROZEN")
def die(msg):
print("FAIL:", msg)
sys.exit(1)
def load_fence(path):
text = path.read_text().splitlines()
allowed, forbidden, mode = [], [], "allowed"
for raw in text:
line = raw.strip()
if not line or line.startswith("#"):
if not line and allowed:
mode = "forbidden"
continue
if mode == "allowed":
allowed.append(line)
else:
forbidden.append(line)
return allowed, forbidden
def main():
if FROZEN.exists():
die("shift is frozen; freeze officer must clear the stamp")
if not ROSTER.exists():
die("missing .agent/roster.yaml")
roster = yaml.safe_load(ROSTER.read_text())
cap = roster.get("floor_captain")
frz = roster.get("freeze_officer")
if not cap or not frz:
die("captain and freeze officer are required")
if cap == frz:
die("captain cannot be freeze officer")
if roster.get("status") != "open":
die("window is not open")
allowed, forbidden = load_fence(FENCE)
if not allowed:
die("fence has no allowed roots")
changed = CHANGED.read_text().splitlines() if CHANGED.exists() else []
for path in changed:
if not path:
continue
if any(path.startswith(item) for item in forbidden):
die(f"forbidden path {path}")
if not any(path.startswith(item) for item in allowed):
die(f"unfenced path {path}")
print("PASS: roster and fence hold for this shift")
if __name__ == "__main__":
main()
Collect the changed paths with git, not with memory. Memory lies after a long agent loop. Git still tells the truth on the branch.
mkdir -p .agent
git fetch origin main
git diff --name-only origin/main...HEAD > .agent/changed.txt
python3 check_roster.py
A late window needs one more command from the freeze officer. That officer writes a freeze stamp when the fence breaks. The captain must not delete that stamp during the window.
# proposed freeze stamp, issued only by the freeze officer
date -u +%Y-%m-%dT%H:%M:%SZ > .agent/FROZEN
echo "frozen by ${USER}" >> .agent/FROZEN
git add .agent/FROZEN
git status --short
The next loop reads that stamp first. A frozen tree is a closed stove. No new agent cycle starts on a frozen tree.
Now the hour itself follows a fixed pass. The clerk posts a brief with a single outcome. The captain copies that brief into the agent session.
The steward watches the name-only diff, not the chat. The freeze officer watches the fence file and the stamp. Talk stays cheap. Paths stay expensive.
Shared labs often park idle agent work on spare compute. MonkeyCode offers free model access and a free server option for that class of shift. Disclosure: This article was prepared as part of MonkeyCode's product outreach.
The roster still applies if another host provides the compute. The seats do not change with the vendor. Compute without a named floor is still an unowned kitchen.
Free shared hours tempt teams to skip the roster. The room feels empty, so the rules feel heavy. That is the trap that burns the branch.
A good shift lasts one window, then stops. The captain closes the status field on the card. The steward pastes the name-only diff under that card.
The freeze officer confirms the stamp is absent or still honored. Then the branch waits for ordinary review. Ordinary review is not this playbook.
Tests, design, and product intent stay elsewhere. The roster only answers who owned the hour. It does not certify the patch as correct.
This method will fail some rooms on contact. A solo hobby clone does not need four seats. A production incident already has a commander.
A regulated merge needs more than a wiki card. Those rooms should not borrow this as cover. The fence also fails when roots are too wide.
Allowing the whole repo is a paper wall. Forbidden lists that omit secrets are worse than no list. The steward must read the fence before the window opens.
Chat pins are not a roster in any useful sense. A thread will scroll off the shift. The wiki card stays at a stable URL.
The checker reads files, not vibes from the thread. That is the whole point of the floor watch. Vibe sessions can still happen inside the fence.
Engineering starts when names, paths, and a freeze exist. Calling the session engineering without those three is a costume. The roster removes the costume before main sees the diff.
Teams already using that free server can pin the roster beside the workspace. Run the checker before the window, not after the merge. The floor stays named, and the diffs stay owned.
Top comments (0)