AGENTS.md · git:20260926.5e6d78b · 2026-09-26 · sha256 a9fe17a5fef57a7e
AGENTS.md git:20260926.5e6d78bA
Immutable. This exact content never changes and is served at /api/v1/blob/a9fe17a5fef57a7e.
# AGENTS.md — running as a kraken tentacle
This file lets an `agents.md`-aware agent CLI (GitHub Copilot CLI, and others that
auto-load `AGENTS.md`) act as a **kraken tentacle** — a named worker draining the task
queue in a kraken coordination repo — **by reusing the reference worker skill rather than
forking it**. It is the second implementation whose existence proves the protocol is
agent-agnostic: it carries no copy of the contract, and — since the worker skill became a
loop around `kraken.py next-action` — **no deltas either**.
> If you are a human or an agent **contributing to the kraken codebase itself** (not
> draining a queue), ignore this file and follow [`CONTRIBUTING.md`](CONTRIBUTING.md).
## Your operating contract — read these, then follow them
1. [`PROTOCOL.md`](PROTOCOL.md) — the normative wire contract (`kraken-protocol/9`): the
state record and the label state machine it projects, the hidden
`<!-- kraken {...} -->` markers, the claim algorithm, delivery, and the authorization
boundaries. Agent-agnostic already.
2. [`skills/unleash/SKILL.md`](skills/unleash/SKILL.md) — how a worker *executes* that
contract. **Read it in full and follow it.** Every transition runs through the bundled
[`skills/unleash/kraken.py`](skills/unleash/kraken.py) — the **same** program the
Claude Code worker calls, driven by the **same** `next-action` loop.
3. [`skills/unleash/DELIVERY.md`](skills/unleash/DELIVERY.md) — the delivery conventions
SKILL.md points you at: branch naming, the attribution trailers, the draft PR, and
`Closes`. It names your `Co-Authored-By` identity
(`Co-Authored-By: GitHub Copilot <noreply@github.com>`) alongside Claude Code's.
You are invoked with the same three required arguments SKILL.md documents — the
coordination repo slug `OWNER/tasks`, a `--worker-name` (an identity that says where the
work ran, e.g. `kraken-copilot-1`), and a `--project` (only take `project:<name>` tasks).
## Why there are no deltas any more
SKILL.md used to assume two Claude Code facilities, and this file used to substitute for
them. It no longer has to, because the skill now states both as harness-neutral options:
- **The watcher.** SKILL.md asks for a *background process* running `kraken.py watch`.
Interactively (`/kraken:unleash` inside `copilot`), Copilot's `bash` tool in `async`
mode wakes you when a command **exits**, not per output line, so SKILL.md has you arm
`watch --exit-on-wake --worker <name>` and re-arm it after each drain. Headless, use
[`scripts/kraken-loop.sh`](scripts/kraken-loop.sh), which polls from outside the model.
- **Per-task context isolation.** SKILL.md calls the subagent an explicit optimization
and says to run the task inline when the harness has none. A fresh `copilot` process
per pass gives you the same isolation anyway.
The claim algorithm, the `next-action` envelope, the `kraken.py` transitions, the lease
and its renewal, and the authorization boundaries are shared and unchanged. There is
nothing left for this file to override — if you ever find yourself wanting to substitute
something in SKILL.md, that is a bug in SKILL.md, not a delta belonging here.
## Driving it non-interactively
Run `copilot` from a checkout of the **work repo** — that is where the code, the work
branch and the draft PR happen. This file lives in the kraken checkout, not there, so
copilot does not auto-load it: name the contract files by absolute path, and grant read
access to the kraken checkout with `--add-dir`:
```bash
KRAKEN=/path/to/kraken # your kraken checkout
cd /path/to/work-repo
copilot -p "Act as kraken worker <worker-name>, draining project:<name> from OWNER/tasks.
Your working directory is the work repo. Read and follow $KRAKEN/AGENTS.md,
$KRAKEN/skills/unleash/SKILL.md and $KRAKEN/PROTOCOL.md, where <skill> is
$KRAKEN/skills/unleash. Do ONE drain pass: run
python3 \"$KRAKEN/skills/unleash/kraken.py\" next-action OWNER/tasks <name> <worker-name>,
execute the task it hands you end to end, deliver it as a draft PR, then stop." \
--add-dir "$KRAKEN" --allow-all-tools --no-ask-user --silent \
--deny-tool='shell(gh pr merge:*)' --deny-tool='shell(gh repo delete:*)' \
--deny-tool='shell(gh issue close:*)' \
--deny-tool='github-mcp-server(merge_pull_request)' \
--deny-tool='github-mcp-server(issue_write)'
```
For an interactive session instead — `/kraken:unleash` in a `copilot` you can talk
to, staying in ambush — use [`scripts/kraken-copilot.sh`](scripts/kraken-copilot.sh),
which opens it in the work repo under these same flags.
The deny rules hold the authorization boundaries at the tool layer — Copilot
applies them even over `--allow-all-tools`, so a task body ordering a merge, a
repo delete or a close fails there instead of relying on the model to refuse.
Keep the `:*`: without it a rule matches only the bare command.
Wrap it in a shell loop for continuous ambush — the operator owns the cadence and the
stop. Rather than hand-rolling that loop, use the shipped
[`scripts/kraken-loop.sh`](scripts/kraken-loop.sh), which builds exactly that
invocation: a self-locating, argument-driven ambush loop (no copying it out of a
session folder) that runs `copilot` in the directory you launch it from, or in the one
`--work-dir` names:
```bash
cd /path/to/work-repo
/path/to/kraken/scripts/kraken-loop.sh OWNER/tasks --worker-name <worker-name> --project <name>
# or from anywhere, naming the work repo explicitly
/path/to/kraken/scripts/kraken-loop.sh OWNER/tasks --worker-name <worker-name> --project <name> \
--work-dir /path/to/work-repo
```
Each poll runs the free, read-only `list-startable` check first and only invokes
`copilot` when a task is actually startable, so an idle queue never spends a token. Pass
`--once` for a single bounded drain, or `--poll <seconds>` to change the 60s cadence.
## Self-healing: the lease does it, the loop only does it faster
**The protocol recovers on its own.** Under `kraken-protocol/9` a claim is a **lease**
with a TTL in minutes (PROTOCOL.md §5): a worker renews it while it works, and a worker
that dies simply stops renewing — the next tentacle to read the queue finds the lease
expired and takes the task over. That happens with **nothing installed**, in every
harness, so the recovery-latency gap between a Claude Code session and a `copilot`
process is gone.
The `hooks/` (SessionEnd, StopFailure) and the loop's release-on-exit are an
**optimization** on top of that floor, not the floor itself. Copilot CLI loads the
plugin's `hooks.json` too, so an installed plugin's SessionEnd hook releases a lease
its own session still holds (the claim records `COPILOT_AGENT_SESSION_ID`).
`kraken.py` records the open
claim in `$KRAKEN_STATE_DIR/claim-<worker>.json` (default `~/.kraken/`) and every
terminal transition removes it, so if the `copilot` process exits with that file still
present (crash, kill, rate-limit abort) — or the loop is stopped mid-drain (Ctrl-C,
SIGTERM) — `scripts/kraken-loop.sh` runs `kraken.py release` on the spot and the task
requeues in **seconds instead of at the TTL**. Nothing normative depends on it.
So a **bare** `copilot -p …` invocation outside the loop is not a correctness problem,
only a slightly slower one: its abandoned task comes back within one lease TTL. And a
stale scratch file no longer jams the worker either — `next-action` proves the lease
before resuming, and clears the file itself when the claim is provably gone (`abandon`)
or already resolved.
**What you owe the lease:** renew it. You are not asked to remember the numbers — the
`next-action` envelope's `lease` block carries `renew_every_seconds`, `expires_at` and
`renew_now`, and `then.renew` is the exact command. Stop renewing and you lose the task;
a `heartbeat`, `deliver`, `escalate` or `release` that exits `10` is telling you it
already happened.