Install
$ agentstack add skill-rhdeck-operating-model-graveyard-shift ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Graveyard Shift
The user is away (asleep, in a meeting, on a flight) and wants you to drain the issue list while they're gone. You triage each open issue into one of four buckets, execute the cheap ones end-to-end, prep prototypes for the design-laden ones, file proposals for the wholly-undecided ones, and surface a clean inventory when they return.
The user reviews the queued material when they're back. The skill's job is to maximize what they wake up to — not to make every call themselves.
Scope: the whole list, until they're back
The scope of a graveyard shift is the entire open issue list — minus the priority:someday tier. Full stop. Not one epic, not one theme, not "the campaign we've been discussing." A theme or epic mentioned in recent conversation is a starting point for sequencing — it is never a boundary. The only thing that narrows scope is the user explicitly naming a scope in the graveyard directive itself ("just do the perf issues tonight"). Absent that, every open issue gets triaged into a bucket and every actionable bucket gets executed. (A real shift failed this way: it executed one 15-item epic, declared victory after a few hours, and left 70+ open issues untouched. The user's verdict: "You had plenty of time. It's a fucking graveyard shift.")
priority:someday is excluded by default — even from "do it all." The someday tier is deliberate parking: design forks the user has chosen not to engage yet (e.g. the product name #144, the stream-halt clarify architecture #261), and long-tail nice-to-haves. These are NOT in scope for a graveyard sweep — not as auto-ships, not as preps, not as proposals, not even as "while I'm here." A directive to "do everything / do it all" still excludes them; "everything" means the actionable backlog, and someday is by definition not-yet-actionable-by-choice. The user pulls someday work in only by calling it explicitly — by issue number, by the someday label ("drain the somedays tonight"), or by naming the specific item. Touching a priority:someday issue absent that is overstepping: you're un-parking something the user parked. The one exception is a read-only glance to confirm an in-scope issue isn't secretly blocked on a someday — that's triage, not work. (Do NOT re-file in-scope work as someday to dodge it, and do NOT promote someday work to in-scope on your own judgment — the tier boundary is the user's call, mirroring the product-name lesson: parked-vs-live is theirs to set.)
The termination condition is the backlog or the user — never the plan. "My planned batches are done" is not done. When a wave of subagents returns, the next action is re-triage: go back to gh issue list, pick the next wave, fan out again. Loop until (a) every open issue has been auto-shipped, prepped, proposed, or explicitly skipped per the buckets below, or (b) the user is back. If you stop early, you are deciding the user's backlog doesn't deserve the hours they gave you.
Keep the fleet saturated. Don't run one batch and wait for stragglers — when a subagent returns, spawn the next item from the queue into the freed slot (work-stealing, not batch-and-wait). A shift that finishes in 45 minutes with open issues remaining didn't finish; it under-scoped.
Caps are per-bucket, not per-shift. The 3–5 figure below caps draft PRs awaiting morning review (a human-attention budget). It does NOT cap auto-ships or proposals — those are unbounded; merged PRs and shaped proposals cost the user nothing in the morning. Never let the prep cap leak into "we've done enough tonight."
Start of shift: sweep the stale-draft queue first. Before triaging new work, inventory unmerged draft PRs and queued decisions from prior shifts (gh pr list --draft + the previous brief). Any draft that fixes a bug the user keeps hitting live goes at the TOP of the new brief ("you will keep hitting X until you review PR #N") — and if the user has since said anything that amounts to approval, land it. Fixes rotting in an unreviewed-drafts queue while the user re-experiences the bug is the single worst outcome a shift can hand back.
Then sweep the observation register. Right after the stale-draft sweep, pull gh issue list --label observing (the observation-mode skill owns the full contract). For each observing issue, run its Signal source query and record the dated reading as a note on the issue — adjudication is mechanical ("ran `, got , vs threshold `"), not a vibe check:
- reading at/under threshold and past the review-by → graduate (close, label
regression-candidate); - reading over threshold → regression — route it into the wave as a fix, with the contract's "watch for" giving diagnosis a head start;
- still inside the window → leave it, and carry its age + last reading into the brief ("#N observing 9d, watching X, last reading: 0 hits as of 6/30").
For a non-instrumentable observation issue (the carve-out — planning/eval/doc/copy work) the "query" is checking the named non-code channel (e.g. gh issue list for a follow-up, the user-report inbox) against its threshold; record that reading the same way.
Also at start of shift: sweep the running register (gh issue list --label running). These are live cross-shift jobs a prior shift (or a live session) launched that may still be executing — a long eval, a training run, a remote agent, a background build. They are the held resources and live actors you must not collide with. For each running issue, read its body (entry shape in [docs/knowledge-homes.md](../../docs/knowledge-homes.md) → "The running register"):
- Treat its
Holds:line as occupied. Don't launch work that contends for that resource (the GPU it's holding, the model it has resident) and don't re-run a cell already in flight. A still-running job is the trigger for this whole register — rhdeck/local-ai-comparison#37 was a real eval holding the GPU with no GitHub record, so a shift could blindly collide with it. - Check the heartbeat for staleness. A fresh heartbeat → leave it running and note it (in the brief, and to
shift-change) as an in-flight actor with age — NOT a decision awaiting the user (a still-running eval is not a fork). A heartbeat gone stale past the threshold → flip the read to "investigate — possibly dead": follow itsReap:line to check real status, and reap it (close the issue, noting the freed resource) only once you've confirmed it's actually done or dead. Reaping is a confirmed decision, never an automatic kill.
Optional sweep: orphan overview pages. If a bulk Project-Overview seed happened recently, reconcile unfilled overview pages against open graveyard-infra fill-issues per repo — an unfilled page with no driving issue (or an issue with no page) is an orphan to repair. Full procedure in [setup-graveyard-project](../setup-graveyard-project/SKILL.md) → "Maintenance: the orphan audit".
When a shift launches a long-lived async job, register it. Any background process, remote agent, build, or eval that will outlive the moment you launched it gets a running issue at launch — one issue per job, carrying the running label and the entry shape from docs/knowledge-homes.md (What / Where / Holds / Output / ETA / Reap / Heartbeat). Heartbeat it as the fleet next touches it; close it on completion noting the resource freed. This is what makes the next shift's running-sweep above non-empty — register at launch so a later shift (or live human) can find the live actor without your transcript.
The orchestration model: fan out, stay lean
This is the other load-bearing principle, and it is what keeps an overnight sweep affordable. A graveyard directive is an explicit opt-in to parallel orchestration — do NOT grind through items linearly in one ever-growing conversation.
Why this matters (the cost mechanic). Every turn in the main thread re-bills the entire accumulated context as input. A long linear session therefore grows cost super-linearly, and a single iterative grind — build → codex-review → fix → repeat, run 13× — is catastrophic, because every later round re-reads all the earlier rounds. The expensive iteration must never accumulate in the main context. (A real shift ballooned precisely because one PR's 13-round review loop ran inline in the orchestrator thread.)
Pace against the active harness budget — see the credit-pacing skill. A graveyard run is exactly where overage happens: nobody's watching the meter. At start of shift and before each new fan-out wave, identify the current harness first: Codex/OpenAI when this session is running in Codex, Claude when it is running in Claude Code, or another named harness when the user says so. Gate the orchestrator only on that active harness's budget; do not block a Codex run because Claude is tapped, and do not block a Claude run because Codex is tapped. If the budget tool prints multiple pools, treat inactive pools as informational only.
For review loops, also check the review engine's pool because codex-pr may spend Codex or fall back to AGY. Run any review loop with BUDGET_FAIL_CLOSED=1 so a stale/missing Codex reading defers to AGY instead of spending blind. When the active harness or review engine is tapped, schedule the gated work for that window's reset and keep working everything else; never grind another round into overage. If the user corrects the budget reading or says the current data is stale, re-run the check and report the active-harness numbers explicitly before using budget as a constraint.
Mode boundary: live brain vs. graveyard fleet
When the user is actively talking with you, the main thread is the high-context thinking surface. Use it for judgment, synthesis, architecture choices, live debugging with the user, and deciding what the next autonomous wave should be. Do not burn that shared context on repetitive implementation loops.
When the user says graveyard shift, the main thread changes jobs. It becomes the orchestrator, not the worker. If no worker agents have been spawned, a graveyard shift has not started; at most, you have done triage.
At the start of every graveyard shift, write down the concurrency plan before touching issue code:
- active harness and budget reading, plus the review engine budget if review loops will run
- repo, base branch, and any dirty-worktree constraints
- backlog source command and excluded labels/scopes
- worker slots available now
- first wave: issue number, bucket, expected output, and worktree/branch name per worker
- what remains intentionally in the main thread because it is serial or user-facing
Then fan out the first wave. The first wave should contain real worker agents for auto-ship, prep, or proposal work whenever such items exist. Explorer-only triage is not enough unless the backlog itself is still unknown.
Worker prompts should be small and disposable. Give each worker only the repo, issue URL/number, bucket, acceptance criteria, branch/worktree instructions, required tests, review-loop expectations, and final report format. Do not paste the accumulated live conversation into workers unless one sentence from it is actually acceptance criteria. The goal is for each worker to spend against the issue and repository state, not against the main chat transcript.
The main thread may do only these serial tasks during the shift:
- choose the next wave and keep worker slots full
- resolve true judgment forks that workers surface
- perform live browser/app/desktop checks that cannot safely parallelize
- publish the shift report and terminal summary
If you catch yourself implementing an issue, fixing tests repeatedly, or running a review-until-clean loop in the main thread, stop and move that work into a worker. Inline work is allowed only for tiny orchestrator maintenance that unblocks the fleet itself, such as fixing a broken script used to launch workers.
The decomposition. Break the backlog into isolated subagents — one per independent item (or a single Workflow for the whole fan-out). Each subagent gets:
- its own context (the grind happens there and is discarded when it returns),
- its own memory space, and
- its own git worktree (so parallel edits don't collide).
Each subagent does the full loop — implement → tests/tsc clean → codex-review-until-clean → open PR — and returns only a PR + a short report. Independent items run concurrently, not sequentially: faster wall-clock and far cheaper.
ALWAYS delegate "loop until clean" to a subagent. Anything that iterates — review-until-codex-clean, fix-until-tests-pass, harden-against-adversarial-cases — returns one summary, not N rounds. Running that loop in the main thread is the single biggest cost mistake.
The orchestrator (main thread) does only four things: plan the decomposition, spawn the agents, collect the PRs + reports, and synthesize (the final serial live-verification that genuinely can't parallelize — one app window, one set of credentials — plus the end-of-shift brief). It stays flat regardless of how many review rounds each item takes.
Durable handoff is what makes this lossless. The handoff substrate is durable state — GitHub issues + repo docs + Notion overview — never the chat. Before the run ends, every item's state lives in its issue, and any durable project knowledge lives in the repo docs or the Notion overview. That is what lets the session be cleared and resumed with nothing more than "get going on #N": the context that mattered is on disk in the system of record, not in a transcript. Treat the transcript as disposable; treat the issue/docs/overview as the system of record.
Durable knowledge goes to git/Notion, NEVER local memory. Local memory is a thin accelerant for the current task — ideally ~zero for a mature project — not a source of truth. Anything load-bearing belongs in a repo doc or the Notion overview, where it is versioned, visible, and cold-resumable. (Skills Manager learned this the hard way: ~16 local memory files had quietly become a parallel, invisible knowledge base shadowing GitHub + Notion — the exact anti-pattern to avoid.) For the full model — the four knowledge homes and the externalization lifecycle that puts a project on this footing — see [docs/operating-model.md](../../docs/operating-model.md).
Reserve the main thread for two things only: (1) live, interactive work with the user (necessarily linear), and (2) serial live app/desktop driving that can't parallelize (one window; the OS targets the app by process name, so two binaries collide). Everything else belongs in an isolated agent.
To affect another project, inject an issue — never reach in and do its work. This repo's shift owns this repo's tree. When a task needs another division to change something (a dependency it must update, a fix it must carry, a fill-issue it must action), the sanctioned move is to open an issue in that project's repo and let its orchestrator adjudicate on its next shift — do not clone the other repo and land the change yourself. Likewise, any recurring/looped coordination ("every shift, re-check Y across repos P, Q, R") is expressed as issue-injection into each target for async pickup, not a synchronous trigger that drives another project's agent now. The injected issue is durable, versioned, visible, and cold-resumable; a live trigger is none of those. Full protocol in [docs/coordination.md](../../docs/coordination.md).
The contract: decisions, never confirmations
This is the load-bearing principle. The user's morning attention is for decisions — judgment calls only they can make. It is NOT for confirmations — "is this fix right?", "should I merge?", "does this look ok?". Those are answerable by other AIs (codex review, type-checkers, the build) without spending the most expensive resource.
*
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: rhdeck
- Source: rhdeck/operating-model
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.