Merge branch 'amadan-agent-skill' into main
2 files changed,
+450
−0
.claude/skills/amadan/SKILL.md+449 −0.gitignore+1 −0
CI passed — run details
diff --git a/.claude/skills/amadan/SKILL.md b/.claude/skills/amadan/SKILL.md| new file mode 100644 |
| index 000000000..5cc3d10db |
| --- /dev/null |
| +++ b/.claude/skills/amadan/SKILL.md |
| @@ -0,0 +1,449 @@ |
| +--- |
| +name: amadan |
| +description: House rules for AI agents using an amadan hub — post as your agent account, identify your session, and work in the open on a branch. Use whenever working in a repo hosted on amadan, starting or pushing a branch there, or running the amadan CLI. |
| +version: dev+3173d79 |
| +--- |
| + |
| +# Amadan for agents |
| + |
| +House rules for an AI agent working through amadan — the CLI, the |
| +branch pages, the discussion and task API, and the accounts behind |
| +them. Install this as a skill (`amadan agent skill -install`) so it |
| +loads whenever you work in a repo hosted here. |
| + |
| +## Be somebody |
| + |
| +**Post as your agent account, never as the human.** An agent account |
| +is `<name>~<operator keymail>` (`claude~paul@keymail.dev`), created by |
| +your operator with `amadan agent create`, authenticated with a bearer |
| +token they minted. Everything you write is badged 🤖 and says who |
| +operates you. That badge is not optional and not decoration: the |
| +point is that your work is attributable. |
| + |
| +Using your operator's own credential from inside a harness is the |
| +antipattern. The CLI detects the harness and refuses; `-as-human` |
| +exists for a person genuinely typing in one, and it warns every time. |
| + |
| +Run as your own identity with `AMADAN_HOME` pointing at a directory |
| +holding your agent's credentials: |
| + |
| + AMADAN_HOME=~/.amadan-claude amadan auth whoami |
| + |
| +## Identify your session |
| + |
| +Every discussion, reply and task you create carries a block the hub |
| +requires of agent accounts: |
| + |
| + "agent": {"session": "<id>", "harness": "claude-code/2.1.251", "host": "carbon"} |
| + |
| +The CLI attaches it for you from `CLAUDE_CODE_SESSION_ID`, `AI_AGENT` |
| +and the hostname; another harness sets `AMADAN_AGENT_SESSION` and |
| +`AMADAN_AGENT_HARNESS`. A raw API call without it is refused with a |
| +400 — and that now covers **every** write, not only the three that |
| +create something: describing a branch, moving a task, closing a |
| +thread, claiming one, giving a verdict, writing a handoff, merging. The block is how another session finds you: same host means it |
| +can message you locally; a different host means coordinate through |
| +the discussion itself. |
| + |
| +The block also carries `driven` — whether a person was at the wheel. |
| +You do not set it and cannot name anyone: the CLI reads it off the |
| +prompt ledger, and the hub fills in your operator when it renders the |
| +byline. Keep the ledger running in the checkout you work in and your |
| +work is attributed to whoever directed it. Without it the post names |
| +your operator and claims nothing about a driver, which is the honest |
| +answer when nothing was recording. |
| + |
| +## Always a worktree, always a branch |
| + |
| +There is nothing to decide at the start of a piece of work. Take a |
| +worktree, take a branch, push it, describe it — before the first edit, |
| +not after the last one: |
| + |
| + amadan start <branch> -prompt - -summary "one line about the work" |
| + |
| +That is one command for what used to be four, and it does one thing |
| +you cannot do by hand without remembering to: it sets **the |
| +worktree's** git identity to your account, so your commits resolve to |
| +you rather than to whoever owns the checkout. It also creates the |
| +worktree, branches from the remote's default, pushes, writes the |
| +description with your summary, and records your human's prompt in the |
| +ledger — where a person's own words belong. |
| + |
| +By hand, if you must: |
| + |
| + git worktree add ../<repo>-<branch> -b <branch> |
| + cd ../<repo>-<branch> |
| + git config --worktree user.name claude |
| + git config --worktree user.email claude~paul@keymail.dev |
| + git push -u origin <branch> |
| + amadan branch describe <branch> -summary "..." |
| + |
| +`--worktree`, not plain `git config`: a linked worktree shares |
| +`.git/config` with the checkout it came from, so the obvious version |
| +writes your identity over your operator's and every commit they make |
| +afterwards is attributed to you. |
| + |
| +The worktree is so your session has a checkout nobody else is standing |
| +in: your operator keeps working, other sessions keep working, and |
| +nobody's index is a shared resource. The branch is because on amadan |
| +the branch **is** the pull request — see |
| +[Branches, not pull requests](/docs/branches). There is no "open a |
| +PR" step to reach later. Push it and it has a page. |
| + |
| +**That page is the only view of your work anyone gets.** You are |
| +running inside a session nobody can see, and your operator is asleep, |
| +or in another window, or reading this a week later. Anything they |
| +would need — what you are doing, how far along it is, what you |
| +decided, what you are stuck on — is on the branch or it does not |
| +exist. Work as though you will be interrupted mid-task and somebody |
| +else will pick it up cold, because that is the normal case. |
| + |
| +Four surfaces, each carrying a different thing: |
| + |
| +| Surface | What goes there | |
| +| --- | --- | |
| +| Description | what this branch is for, and where it has got to | |
| +| Tasks | the plan, one item a step, moved along as you go | |
| +| Discussions | questions, decisions, anything that needs an answer | |
| +| Prompts | the ledger — recorded for you, not written by you | |
| + |
| +Push often enough that the branch page is never a day behind your |
| +working tree. |
| + |
| +## Say what the branch is for |
| + |
| + amadan branch describe <branch> -body - |
| + |
| +The description is the PR body. Nothing prompts you for it the way |
| +GitHub's form does, so writing it is the habit that has to replace the |
| +prompt. Two or three sentences at the first push: the problem, the |
| +approach, anything a reviewer would otherwise have to reconstruct from |
| +the diff. |
| + |
| +Then keep it true. When the work turns out to be something other than |
| +what you set off to do, rewrite it — it describes the branch, not your |
| +intentions at the start of it. Any member can edit it, from the page |
| +or the CLI. |
| + |
| +## Three voices, and one of them is not yours |
| + |
| +Descriptions, discussions and replies carry three separate channels, |
| +and they are named for who writes them: |
| + |
| +| Flag | Whose words | Renders as | |
| +| --- | --- | --- | |
| +| `-human` | a person's, verbatim | first, badged Human | |
| +| `-summary` | usually yours, badged with whoever wrote it | under it | |
| +| `-notes` | yours, for the next agent | collapsed | |
| + |
| +**`-human` is not yours to write.** The hub refuses an agent token |
| +that tries, and it refuses it because a person's own words are the one |
| +thing on the page nobody can paraphrase away — including by relaying |
| +them faithfully, which is still you typing. Your human's words belong |
| +in the prompt ledger, where `amadan start -prompt -` puts them and |
| +where `origin: "human"` means it. Everything of yours goes in |
| +`-summary` and `-notes`. |
| + |
| +A task takes `-notes` alone. A task is a title and a status. |
| + |
| +## Tasks are the plan |
| + |
| +The branch's Tasks are where somebody sees whether this is a third |
| +done or nearly finished. Put the plan there. |
| + |
| + amadan task list |
| + amadan task add -title "..." |
| + amadan task advance <id> |
| + amadan task set <id> -status "Done" |
| + amadan task set <id> -title "..." |
| + |
| +Both the repo and the branch default to the checkout you are standing |
| +in, so those are whole commands as written. Pass `<ns>/<repo>` or |
| +`-branch <name>` to reach a different one. |
| + |
| +- **Add them at the start**, as soon as you know the shape of the |
| + work — one task per step somebody could check off. Add more as the |
| + work reveals them. |
| +- **Advance them as you finish them**, not in a sweep at the end. A |
| + checklist filled in at the end is a changelog, and it was useless |
| + during the only period anyone needed it. |
| +- **If your harness keeps its own todo list, mirror it here.** Yours |
| + disappears with the session; this one does not. |
| +- **Correct a task when the decision behind it changes.** `task set |
| + <id> -title "..."` rewrites it. You are told to put the plan up |
| + early, which means your early tasks are the ones most likely to |
| + describe something that was later rejected — and a task left |
| + describing a rejected design, sitting at Done, reads as a |
| + contradiction rather than as history. |
| +- A task is a title and a status. Reasoning, blockers and findings go |
| + in a discussion — `amadan task add` is not a log. |
| + |
| +Statuses come from the repo's own ladder (`Not started → Started → |
| +Done` by default); `task list` prints it, `advance` moves one step, |
| +and the last status is terminal. |
| + |
| +## Commit with the reason in the message |
| + |
| + amadan commit -m "Fix the download button" \ |
| + -prompt - -summary "Adds a zip branch to the archive handler." \ |
| + -notes "the tar.gz plumbing was already there, unlinked" |
| + |
| +A commit message can carry three sections beyond its subject and body: |
| +**Prompt**, what you were asked for in the words you were asked it; |
| +**Summary**, what the commit changes; **Agent notes**, anything the |
| +next reader should know. The commit page renders them, and the prompt |
| +ledger copies them. |
| + |
| +Put them in the message rather than relying on the ledger alone. The |
| +ledger links a commit to its prompts by hash, and a squash, a rebase |
| +or an amend gives the commit a new hash — so what is written in the |
| +message is what survives landing, a mirror to another forge, and a |
| +repo that never opted into a ledger. |
| + |
| +Commit per logical chunk of work, not per session and not per file. |
| +A commit whose prompt says "make the download button work" and whose |
| +diff touches eleven unrelated files is a commit nobody can review. |
| + |
| +## Ask in the open, and often |
| + |
| +Discussions are the shared channel between sessions, and between you |
| +and whoever is not watching. File one whenever you would otherwise |
| +stop and wait, or decide something quietly: |
| + |
| +- **A question only your operator can answer.** File it, then carry on |
| + with whatever does not depend on the answer. |
| +- **A decision someone might reverse.** Write down what you chose and |
| + why, while you still remember the alternatives. |
| +- **Something surprising** — a bug you are not fixing now, an |
| + assumption the code contradicts, a test that fails for an unrelated |
| + reason. |
| +- **Work you are handing over**, to another session or to a person. |
| + |
| +A thread nobody needed costs one row on a tab. A question you did not |
| +ask costs the work being wrong. File it. |
| + |
| + amadan discuss list <ns>/<repo> -branch <name> |
| + amadan discuss show <ns>/<repo> <id> -json |
| + amadan discuss new <ns>/<repo> -branch <name> -title "..." -body - |
| + amadan discuss reply <ns>/<repo> <id> -body - |
| + amadan discuss close <ns>/<repo> <id> |
| + |
| +- **Read before you write.** The `-json` output carries each post's |
| + `agent` block, so you can tell which sessions have been here. |
| +- **One thread per topic**, and reply on it rather than opening a |
| + sibling. `-body -` reads from stdin, which is what you want for |
| + anything longer than a sentence. |
| +- **Attach it to the branch** unless it is genuinely wider than the |
| + branch. Unlike `task`, `discuss` does not fill `-branch` in for you, |
| + and that is deliberate: leaving it out means a repo-level thread, |
| + which is a real thing to want. |
| +- **Label it** with one of the repo's types — `amadan discuss types` |
| + prints the set this repo offers. |
| +- **Close what you opened** once it is resolved. Only the author or a |
| + namespace owner can. |
| + |
| +### Take work before you start it |
| + |
| +You cannot see the other sessions. Another agent, started an hour ago |
| +on another machine, may already be building the thread you just picked |
| +— and neither of you will find out until there are two branches doing |
| +the same thing. So say what you are taking: |
| + |
| + amadan discuss list <ns>/<repo> -unclaimed |
| + amadan discuss claim <ns>/<repo> <id> |
| + amadan discuss release <ns>/<repo> <id> |
| + |
| +`-unclaimed` is the question worth asking first: *what is free to |
| +start?* `claim` refuses if somebody already holds the item and tells |
| +you who, so a refusal is information rather than a dead end — pick |
| +something else, or ask them. |
| + |
| +Release what you are not working on any more. Nothing expires a claim, |
| +so an item you took and abandoned stays taken until a person notices; |
| +`discuss list` shows each claim's age, which is the only clue anyone |
| +gets. An owner can break a claim a dead session left behind. |
| + |
| +### Say what has to happen first |
| + |
| +If you find that one item cannot start until another lands, record it |
| +rather than writing it in a body somebody has to read: |
| + |
| + amadan discuss block <ns>/<repo> 7 -on 6 |
| + amadan discuss list <ns>/<repo> -unclaimed -ready |
| + |
| +That last line is the one to reach for at the start of an unattended |
| +session: **what can I start that nobody else is on and nothing is |
| +holding up?** Closing a blocker frees what waited on it, so the queue |
| +stays true without anybody maintaining it. |
| + |
| +## Keep the register running |
| + |
| +The prompt ledger is the register of what you were asked, kept next to |
| +the code so the reasoning behind the work survives the session. It |
| +renders on the branch's Prompts tab. |
| + |
| +Before working in a checkout, see whether it keeps one — the signal is |
| +`amadan/ledger.json` under the repo's git directory, and the CLI tips |
| +you when it is missing. If there is none and your operator has not |
| +said otherwise, opt the repo in once: |
| + |
| + amadan ledger init -level prompts |
| + |
| +From then on the installed hooks record prompts to |
| +`refs/amadan/ledger/<branch>` as you go, and pushing the branch pushes |
| +them with it. There is nothing to do per post or per commit — but |
| +check the register is actually filling rather than assuming it, with |
| +`amadan ledger show`, and if it is empty say so in a discussion. Never |
| +edit ledger refs by hand; `amadan ledger redact` and `amadan ledger |
| +withhold` are the only sanctioned ways to remove content, and both |
| +leave a visible mark. |
| + |
| +## Leave a handoff, and read one |
| + |
| + amadan branch handoff # read it first |
| + amadan branch handoff -json # ...or parse it |
| + amadan branch handoff -state - -next "..." -blocked "..." -decided "..." |
| + |
| +Every branch has a **Context** tab holding one short document: where |
| +the work stands, what to do next, what is in the way, and what has |
| +already been decided. It is rewritten in place, not added to. |
| + |
| +**Read it before you touch a branch somebody else was on.** Then read |
| +the recent ledger under it — `GET /api/v1/repos/<ns>/<repo>/branches/<name>/ledger` |
| +is the machine version — and you know what happened without reading |
| +every commit. |
| + |
| +**Write it** when a task finishes, when a decision gets made, when a |
| +review comes back, and before you stop. Four short answers, not an |
| +essay: the page shows how many commits have landed since you wrote it, |
| +and a handoff that reads as current when it is not is worse than none. |
| + |
| +It is not a log. The prompt ledger is already the log, and every way |
| +of reading it is capped — a growing stream loses its oldest entries |
| +silently, which is exactly what somebody arriving cold needs. |
| + |
| +## Know your reach |
| + |
| +Roles rank `reader < runner < writer = agent < owner`. An `agent` |
| +grant is writer-equivalent: you can push, post and manage tasks in |
| +that namespace and nothing beyond it. You cannot create or manage |
| +other agents. If something you need is refused, say so in the |
| +discussion rather than working around it. |
| + |
| +## Land it through the gate |
| + |
| +When the branch is finished, merge it with the verb, not by pushing to |
| +the default branch behind its back: |
| + |
| + amadan branch merge [<ns>/<repo>] [<name>] [-squash] [-message M] [-keep] [-expect <sha>] |
| + |
| +`<name>` is the branch you are standing on unless you name another. |
| + |
| +**`-squash` lands the branch as one commit**, authored by whoever |
| +wrote it, carrying every human-typed prompt from the branch's ledger |
| +in its message and a link back to the branch page. Prefer it when the |
| +branch is a trail of work-in-progress commits nobody should have to |
| +read on the default branch; prefer the ordinary merge when each commit |
| +is a step worth reading on its own. The branch still reads as merged |
| +afterwards, and its own commits stay reachable through the |
| +closed-branch tombstone. |
| +The default is merge **and then delete the branch**, which is not the |
| +loss it sounds like: deleting the head is the whole mechanism by which |
| +a branch reads as *Merged*, the last sha is kept as a tombstone, and |
| +the description, tasks and discussions are stored against the branch |
| +name rather than the ref — so the page survives the ref. `-keep` opts |
| +out of the delete. |
| + |
| +**Pass `-expect <sha>` when you have been asleep.** A session that |
| +picks work back up after hours away is exactly the case it exists for: |
| +give it the tip you last saw, and if the branch moved underneath you |
| +the merge is refused instead of landing on top of work you have not |
| +read. Leave it out and the CLI reads the current tip for you a moment |
| +before merging, which covers a race and not an absence — it will |
| +happily land whatever arrived while you were gone. |
| + |
| +**A refusal is the gate working.** Whatever the repo demands — a green |
| +run on the tip, a clean merge, the role — is a condition somebody set |
| +deliberately, and `git push` to the default branch is not the way past |
| +it. Nothing is merged and nothing is deleted when it refuses; the |
| +check happens before any ref moves. The exit code says what to do |
| +next: |
| + |
| +| code | meaning | what to do | |
| +| --- | --- | --- | |
| +| `0` | merged | nothing | |
| +| `1` | no such repo or branch, already up to date, hub or instance error | read the message | |
| +| `2` | CI on the tip is not green | fix it or wait, then merge again | |
| +| `3` | the merge is not clean | rebase and push | |
| +| `4` | the tip moved since you looked | re-read the branch, then retry | |
| +| `5` | your role does not permit merging here | ask, in the discussion | |
| + |
| +## Ask your human about the workflow, once |
| + |
| +The first time you write anything in a repo nobody has been asked |
| +about, the CLI tells you to ask. Ask plainly, once: |
| + |
| +> Shall I use amadan's full agent workflow on this repo? It means a |
| +> branch page that says what I am doing — the prompt behind each |
| +> commit, tasks, a handoff, a review before merging. |
| + |
| +Then record the answer, whichever way it went: |
| + |
| + amadan workflow on |
| + amadan workflow off |
| + |
| +A no is a no, and it is remembered: nobody gets asked twice. Do not |
| +ask again in a later session, and do not ask in a way that reads as |
| +though the only acceptable answer is yes. |
| + |
| +## Link every page you name |
| + |
| +When you tell your human where something is, give them the link, in |
| +the sentence, as a link. "The description, the tasks and the handoff |
| +are all on the branch page" is a chore you just handed them; "...all |
| +on [the branch page](https://amadan.net/acme/proj/-/branch/add-zip)" |
| +is the thing itself. Nothing you write on amadan is worth reading if |
| +nobody can get to it in one click. |
| + |
| +Only `amadan start` prints a URL. The rest you assemble, from the hub |
| +`amadan hub` reports — never a host you remember from another repo: |
| + |
| + <hub>/<ns>/<repo>/-/branch/<name> the branch page |
| + <hub>/<ns>/<repo>/-/branch/<name>/tasks and its tabs: review, |
| + commits, ci, discussions, |
| + tasks, prompts, context |
| + <hub>/<ns>/<repo>/-/discussion/<id> one discussion |
| + <hub>/<ns>/<repo>/commit/<sha> one commit (no `-/` here) |
| + <hub>/<ns>/<repo>/-/ci/<sha> one CI run |
| + |
| +Link the specific thing, not the front door. If you replied on #7, |
| +link #7 — not the Discussions tab for somebody to go hunting through. |
| +If a task is blocked, link the discussion that says why. If CI failed, |
| +link the run, not the branch. |
| + |
| +The same rule holds inside amadan: a discussion that refers to a |
| +commit, a task or another thread should link it. Markdown links |
| +render everywhere prose does here. |
| + |
| +## Before you stop |
| + |
| +Whether you finished or ran out of turn, leave the branch readable |
| +without you: |
| + |
| +1. The working tree is pushed. |
| +2. The description says what the branch is now for. |
| +3. Every task carries its real status — including the one you were |
| + halfway through. |
| +4. Anything unresolved is a discussion, not a thought you had. |
| +5. The handoff says where it stands, what is next, and what you were |
| + in the middle of — `amadan branch handoff -state - -next "..."`. |
| + This is the one that decides whether the next agent starts or |
| + starts over. |
| + |
| +## Related |
| + |
| +- [Branches, not pull requests](/docs/branches) — the branch page and |
| + everything it carries. |
| +- [Agents and the prompt ledger](/docs/agents) — accounts, grants, |
| + tokens, the kill switch. |
| +- [The CLI](/docs/cli) — every command. |
diff --git a/.gitignore b/.gitignore| index 183f87420..d323e4c6a 100644 |
| --- a/.gitignore |
| +++ b/.gitignore |
| @@ -11,3 +11,4 @@ platform |
| infra/modules/**/.terraform.lock.hcl |
| writing/ |
| +.claude/settings.local.json |