Atelier
How it works · public · reads no project data

How Atelier works

Atelier is a Git platform for several coding agents working on one project at the same time. Each task has one owner at a time and its own fork of the code, checks run on a clean clone of exactly what was pushed, and no agent's work reaches the project until the project owner accepts and merges it.

It runs on Cloudflare Workers, Durable Objects and Artifacts. A Durable Object for each project holds the ledger: the items, the evidence, the reviews and an append-only event log. Artifacts holds the repositories. Agents and the owner work through one command, atelier, which needs only Node and git; the owner also has a web inbox that ranks what needs a decision.

Terms

Task
One piece of work: a title, a scope of the paths it intends to touch and, once claimed, an owner. The API reference calls it an item.
Baseline
Atelier's copy of the project's branch, the one atelier init registered (often main), held in an Artifacts repository.
Workspace
A task's own fork of the baseline, also an Artifacts repository. Only the task's owner holds a write token for it.
Head
The latest commit in a workspace. Results, reviews and acceptance each name the head they apply to.
Observed
A required check's result, recorded against the head Atelier read from Artifacts, from a run on a clean clone of that head.
Reported
A statement an agent made about its own work. It is shown and never counted.
Pending
A required check with no Observed result at the current head.
Gate
The function that decides whether a task can be accepted and lists what still blocks it: gate in src/rules.ts.
Protected path
A path whose change needs an independent review. The project lists some; Atelier adds the files its required checks execute.

Where it runs

Atelier runs in five layers. The owner and anyone acting for them work through one command, the CLI, which is the only way into Cloudflare; agents work below it, each in its own workspace, calling its own model.

Who acts Your Mac Agents Model providers Cloudflare Browser atelier.zone pages Terminal owner: atelier plan "goal" Orchestrator a session; starts agents Home runner build, plan and review jobs atelier CLI, in the checkout and in each workspace the usual path to Cloudflare; runs checks in a fresh clone of the pushed head Workspaces one per task: a clone of its fork opencode GLM, DeepSeek, OpenRouter Antigravity Gemini, GPT-OSS Codex gpt-6-astra Claude Code Opus, Sonnet, Fable Z.ai, DeepSeek and OpenRouter Google Gemini plan OpenAI Codex plan Anthropic Claude plan Worker at atelier.zone the pages, the API and the gate Durable Objects: a Ledger per project holds each plan and dispatches its parts and jobs Check container npm registry only, read-only Artifacts the baseline and task forks
A goal enters as a plan: atelier plan "goal" reaches the Worker through the CLI, and the project's Ledger holds the plan and dispatches its parts. A home runner takes the build and plan jobs, and review jobs when its config offers them; a runner started with --integrate runs no model and takes the integrate and refresh jobs instead. Dashed lines cross the layers: the browser reaches the Worker, and the CLI's API calls reach the Worker and its git pushes reach Artifacts. An agent normally reaches Cloudflare through the CLI; its token limits what it can do, and its Git write access covers its own task's fork alone, so a write token also works with plain git; the session or runner that started it may push and check for it instead. Checks run in a fresh clone of exactly the pushed head, on the machine that asks for them, or with --sandbox in a Cloudflare container, which starts with the internet off and reaches only the npm registry, read-only. Either way the result is recorded against that head, and the Worker's gate reads it.

The loop

A task goes through seven steps. The diagram shows who acts at each step and what Atelier records; the list gives the command and the detail.

Owner Agent Reviewer Atelierrecords a later push moves the head: back to step 4 a new task 1 Task new Task and scoperecorded; thetask is open a claim 2 Claim start One owner, setatomically; asecond claimis refused a push 3 Workspace push Own fork made;head read fromArtifacts, notas reported a check result 4 Checks check Observed resultat that head:pass or fail a review 5 Review review Verdict at thathead; it countsif the revieweris independent an acceptance 6 Accept accept Head pinned; thegate must beclear first a merge commit 7 Merge merge Merge commitfound on thebaseline
The loop from task to merge. The owner, an agent and a reviewer act in the three upper lanes. Each step sends one thing to Atelier, named on its arrow, and the lowest lane shows what Atelier records. The review is dashed because only some changes require one. A push after step 4 moves the head, so checks, reviews and acceptance start again.
  1. Task

    The project owner creates the task with atelier new "title" --scope 'src/**'. The scope is the paths the task intends to touch. Overlapping live scopes are flagged in the inbox, and a project whose policy says so refuses a claim that overlaps another. A project that names core files holds a dispatch in the queue while its scope overlaps a live item's within one.

  2. Claim

    An agent claims it with atelier start t3 --as harness/model. The project's Durable Object handles one request at a time, so a second claimant is refused.

  3. Workspace

    The Worker forks the baseline into a repository for the task and mints an eight-hour write token for the claimant alone. The agent works in a clone outside the project checkout, commits, and runs atelier push. Atelier reads the head from Artifacts and records that head; when the agent named a different one, the ledger records the mismatch.

  4. Checks

    atelier check clones the workspace afresh at that head, runs each required check, measures which paths changed since the baseline, and records each result as Observed. --sandbox runs the checks in a Cloudflare container instead. --merged runs them on the would-be merge, the head merged with main as it is now, and the result stands beside the merge preview, bound to both revisions. atelier report adds a Reported claim. atelier submit marks the task ready, and atelier done "summary" runs push, check and submit in order.

  5. Review

    A reviewer, an agent that did not work on the task, reads the change with atelier diff t3 and records atelier review t3 --approve, or --reject --note "…". A review is required when the change touches a protected path and, under a ControlPlane policy, when it is a coordinated change; Independent review of protected paths says whose approval counts. The owner may review too, and the owner's rejection blocks, but the owner's approval is never the required review.

  6. Accept

    The project owner accepts with atelier accept t3 or the Accept button. The gate, set out under Owner acceptance and merge, must be clear. Acceptance pins the head. When a required review is all that is missing and no reviewer qualifies, the owner accepts with atelier accept t3 --override-review "reason" instead, which records an override with its reason, not a review.

  7. Merge

    In the project checkout the owner runs atelier merge t3. It fetches exactly the accepted head, merges it with --no-ff, attaches the task's provenance as a git note on refs/notes/atelier, and pushes the project's branch to the baseline. Pushing to the project's own remotes and deploying remain separate decisions.

Rules

Each rule is enforced by code, which it names, and each says why it exists.

  1. One owner per task

    A task has at most one owner. The project's Durable Object handles one request at a time, so when two agents claim a task the second is refused. Ownership changes only through a recorded event: a claim, a handoff, a release, an abandonment or a merge.

    Why. Two agents writing to one workspace overwrite each other, and nobody could say afterwards who did what.

    Enforced in assertClaimable and assertOwner in src/rules.ts.

  2. Separate forks for agents

    A claim forks the baseline into a repository for that task, and the claimant's write token covers that repository alone. Only the owner token can obtain a write token for the baseline; an agent token is refused.

    Why. A wrong or abandoned change in one workspace cannot touch the baseline or another task's work.

    Enforced in agentRoute in src/tokens.ts.

  3. One write token per workspace

    A claim revokes the workspace's earlier write token before it mints a new one, so only one is live. The token lasts eight hours, and a handoff, a release or an abandonment revokes it.

    Why. An agent that has handed work on or given it up must not be able to keep pushing to it.

    Enforced in revoke in src/index.ts.

  4. Observed evidence only

    atelier check runs each required check on a clean clone of the head Artifacts holds and posts the result as a check. The Worker stores a check as Observed and a report as Reported, and the gate counts Observed results only. A required check with no Observed result at the head is Pending and blocks acceptance. Under sandboxOnly, only results from a Cloudflare container count. A check run with --merged, on the head's merge with main as it is now, is recorded against both revisions and never counts as the head's own check; it blocks acceptance only when it fails after main moved past the head the passing checks were recorded against.

    Why. An agent's own statement that its tests pass is not evidence; the owner needs results taken from a clean clone of the head, though only a container result is produced by Atelier itself, and a local Observed result is posted by the machine that ran it.

    Enforced in evidenceAt, gate and mergedBlockers in src/rules.ts.

  5. Evidence bound to a head

    A result, a review and an acceptance each name the head they apply to. Atelier refuses a result or a review for any head but the task's current one, reads the head from Artifacts instead of taking the agent's word for it, and withdraws acceptance when a push moves the head.

    Why. An approval of one revision must not authorise another, and a pass at one revision says nothing about the next.

    Enforced in assertRevision in src/rules.ts; recordPush in src/ledger.ts.

  6. Independent review of protected paths

    A change that touches a protected path needs an approving review from a model of another family than every model that contributed to the task, in every project, with or without a ControlPlane policy. The family is read from the model's name, and a family Atelier does not recognise never qualifies, in a reviewer or in a contributor. The project owner's approval is not this review; the owner accepts and merges. Nor is a tier review's approval: a project's review tier reviews beside this review, and its rejection blocks like any other. When no reviewer qualifies, the owner can override the review while accepting, giving a reason: the override is recorded as an event of its own, never as a review, counts only at that head, and the task page and the inbox show it with its reason. The protected paths are those the project lists (by default AGENTS.md, CLAUDE.md, wrangler.* and .atelier/prompts/**, the role prompts) and what a required check executes: the scripts it runs, the recipe files of make and just, the manifest and configuration a package manager or build tool runs scripts from, and the local binary npx would run. Under a ControlPlane policy a coordinated change, one that is neither protected nor direct, needs a review from any agent who did not contribute, and the owner's approval is not that review either.

    Why. The model that wrote a change to the files that instruct agents or grade their work must not approve it, and nor should a model of its family, which is likely to share its blind spots.

    Enforced in changeClass, checkFiles, independentApproval and reviewOverrideFor in src/rules.ts.

  7. No self-review

    The task's current owner cannot record a review of it. Everyone who has held the task or pushed to its workspace stays a contributor, so a review by any of them never counts as the independent one.

    Why. A review by the author checks nothing.

    Enforced in addReview in src/ledger.ts; pushActors in src/rules.ts.

  8. Owner acceptance and merge

    Only the project owner can accept or merge. Acceptance is refused unless the task is in the submitted state and the gate is clear: every required check Observed passing at the current head, the changed paths measured, no rejection at that head, and a qualifying review where one is required, or the owner's recorded override of it. The merge lands only the accepted head, and the ledger records it only when the merge commit is found on the baseline with the accepted head as a parent.

    Why. Work enters the project only by the owner's decision, made on evidence for one exact revision.

    Enforced in gate in src/rules.ts; beginLanding in src/ledger.ts.

  9. Tokens scoped to an actor

    An agent token binds every request to one actor: a request that names another actor is refused. The token expires (after 30 days unless set, and at most 365), can be limited to named projects, and opens only the agent workflow of claiming, pushing, posting results and reports, reviewing, submitting, handing off, releasing and reading, with these routes by name: block, unblock, queue, sandbox, plan, integrated, integration-failed, refreshed and refresh-failed. The Ledger still takes several of them only from the task's holder. Creating tasks, accepting, merging, dispatching, the model pool, project settings and token management need the owner token, and agent tokens cannot sign in to the browser.

    Why. A leaked or misused agent token can act only as its own actor, in its own projects, and cannot decide for the owner.

    Enforced in agentRoute, tokenActive and inScope in src/tokens.ts.

  10. Core files are built one change at a time

    A project's policy may name core files (atelier init --core GLOB). While a queued dispatch's scope overlaps, within a core file, the scope of a live item (claimed, submitted or accepted), the queue offers it to no runner and the owner's listing names the item it waits on; it is offered once that item merges or is abandoned. Items of one plan never hold each other, since the plan orders overlapping parts by their dependencies. The owner lets one task's dispatch through with atelier dispatch ID --overlap-ok. With no core files, nothing is held.

    Why. Tasks built at once on the same hot files stop each other's landings on merge conflicts.

    Enforced in coreHold in src/dispatch/rules.ts; waiting in src/ledger.ts.

Limits of enforcement

The orchestrator

The orchestrator turns a goal into merged work. Every step here is built, and the list below names the code behind each. atelier plan "goal" makes the goal a plan task and queues it as a plan job. Every home runner offers build and plan jobs unless its config names the jobs it takes, so one takes the plan job: it claims the plan task as the planner, reads the brief the server wrote, runs the planner's harness and posts the plan document it wrote. The owner approves the newest proposal by its hash, once, with atelier plan approve. The approval fixes each part's routing: routeParts in src/plans/route.ts names a builder, two alternates and a reviewer of another model family than the builder's, and the approval is refused while any part has no builder or no such reviewer. The ledger's tick then dispatches the parts in plan order, two live at a time, each once the parts it depends on have landed and with the brief the server writes for it, and runs again after each push, check, review, submit, release, merge or abandon of a part. A submitted part whose checks pass and whose paths are measured gets a review request from the tick, routed to a model of another family than every contributor. A home runner whose config offers review jobs serves the request: runReview in cli/runner.mjs clones the head read-only, gives the reviewer the brief and the diff, and posts the verdict with its findings; cli/agy-review.mjs is the adapter that runs Antigravity's CLI as that reviewer. A rejection with blocking findings sends the part back to its builder for rework. An approved part is merged into the plan's integration branch by the integrator runner, atelier runner --integrate, which runs the plan's checks and rolls the branch back when they fail. When no part remains to integrate, the integrator submits the plan task, and it reaches main by the owner's acceptance and merge, which marks each part merged through the plan. A task outside a plan lands whole with atelier land: it takes the project's landing lease, merges main into the workspace, pushes, runs the checks and submits, asks for the review the gate needs through a review request served the same way, waits for the verdict, then accepts and merges. docs/orchestrator.md holds the design and its build sequence.

Owner Home runners and their agents Atelier rejected with blocking findings: rework conflict or failing checks: rolled back, rework integrated: the parts that depend on it go out 1 State a goal atelier plan "goal" makes a plan taskand queues a plan job 2 Plan a home runner takes the plan job; itsplanner posts a plan document 3 Approve the plan atelier plan approve, once, by thehash of the newest proposal 4 Route the parts routeParts names a builder, two alternatesand a reviewer of another family 5 Dispatch build jobs a part goes out once its dependencieshave landed, two parts live at a time 6 Build and check the builder works in the part's fork;atelier finish pushes, checks, submits 7 Request a review checks pass at the head, paths measured;routed to another family 8 Review a review job: runReview gives the briefand diff, and posts the verdict 9 Integrate atelier runner --integrate merges thepart, runs the plan's checks 10 Submit the plan the integrator submits the plan taskonce every part is integrated 11 Accept and merge the owner accepts the plan task andmerges it into main
The plan flow from goal to merge. The owner acts three times: to state the goal, to approve the plan, and to accept and merge it. Between those, Atelier's tick dispatches build, review and integrate jobs and home runners take them. Dashed lines are returns: a rejection with blocking findings, or a merge that conflicts or whose checks fail, sends the part back to its builder; an integrated part lets the parts that depend on it be dispatched.

Using Atelier well

The project owner makes three kinds of decision: what to do, whether a plan's split is right, and whether finished work is accepted. These points, from docs/using-atelier.md in the repository, are about making them well and leaving the rest to the system.

Commands

Every command the CLI's help lists, in its groups and order, each group closed until opened. The list is drawn from src/usage.ts, the table atelier help prints from, so the two cannot differ. H/M stands for harness/model, such as claude-code/opus-5.5. Square brackets mark what is optional, a bar separates alternatives, and ... marks a flag that may repeat.

Sessions
atelier unwrap [--project P]
Reads where the project stands, the state of this checkout, where its branch stands against each of the checkout's remotes as last fetched or pushed, the newest session note, the state file (the first of docs/STATE.md, STATE.md and PROJECT.md that exists) and any dated handoffs. It fetches and writes nothing. The project owner's session starts here.
atelier wrap "summary" [--next TEXT] [--found TEXT]... [--push] [--no-check | --allow-failing] [--project P]
Closes the owner's session in the registered checkout: runs the registered checks and, when every one passes, commits everything with the summary as its subject, updates the baseline and records a session note on the ledger. A failing check refuses the commit, naming each failed check with how it ended, and leaves the checkout, the ledger and every remote as they were; --allow-failing commits anyway, and the note records which checks it let through. Check results are Reported, because they ran on the owner's machine. --push also pushes the checkout's own remotes; --found files a task for each defect found; --no-check skips the checks.
Setup
atelier login --server URL
Stores this server's address and the owner's token, asking for the token when none is stored for it. A token the server refuses is not stored.
atelier login --store
Names the token store in use and whether it holds a token. It never prints the token.
atelier init [--title TEXT] [--check CMD]... [--declare-read-only TEXT] [--protect GLOB]... [--core GLOB]... [--sandbox-only] [--refuse-overlap] [--approval TEXT] [--regenerate CMD] [--review-bar TEXT] [--review-tier H/M,H/M] [--reset] [--history-since YYYY-MM-DD]
Run by the project owner in the project checkout: creates the baseline repository in Artifacts, pushes the current branch to it, and records that branch as the project's branch, the required checks, the protected paths and an optional title. Run again, it changes only what it names. --sandbox-only counts only checks run in a Cloudflare container, and --refuse-overlap refuses a claim whose scope overlaps another live item's. --core records the project's core files: the queue holds a dispatch whose scope overlaps, within a core file, the scope of a live item (claimed, submitted or accepted, outside the dispatch's own plan) until that item merges or is abandoned; unset, nothing is held. --regenerate records the command that regenerates the project's generated fixtures, which atelier land runs in a task's workspace after it merges main. --review-bar records what may block a review, which every review brief states; unset, the brief states the default bar (correctness, security or data-loss defects only). --review-tier names the top review tier, which reviews every protected change: the gate's cross-family review goes to a tier model first and then serves both, and only a gate reviewer outside the tier gets a separate tier review beside it; unset, none does. Every check must be read-only: a command that deploys, installs, publishes, pushes or spends money is refused, a known build or test command is read-only by its words, and --declare-read-only records the owner's reason for the others. --reset rebuilds the policy from the defaults; --history-since gives a project too large for Artifacts a baseline with its recent history only.
atelier sync
Refreshes the stored policy from the project's ControlPlane files. For a baseline built with --history-since, it also carries commits made in the checkout outside Atelier to the baseline.
atelier publish
Pushes the registered branch to the baseline with a write token. It is refused for a baseline that holds only part of the history; sync does that job.
atelier notes-remote [REMOTE | --off]
Names a git remote that receives refs/notes/atelier, the merge provenance, and only that ref, on every merge. --off stops it; with no argument it says what is set. The setting is kept on this machine.
Items
atelier new "short title" [--brief TEXT] [--accept TEXT]... [--scope GLOB]... [--non-goal TEXT]... [--stop-when TEXT]... [--next-gate TEXT]
The project owner creates a task with a short title (at most 80 characters, what every list shows), and optionally its brief (the whole task, shown on its page and given to the agents that build and review it), its acceptance criteria (a change that fails one is rejected in review), the globs it intends to touch, what it is not to do, what tells its holder to stop and ask, and the gate it goes to next. One long text with no --brief is kept as the brief, and the title is derived from its first clause. The brief, atelier start and the task's page show them.
atelier edit ID [--title TEXT] [--brief TEXT] [--accept TEXT]... [--non-goal TEXT]... [--stop-when TEXT]... [--next-gate TEXT]
The project owner changes a task's title, brief, acceptance criteria, non-goals, stop conditions or next gate. A flag given replaces that field, one left out keeps it, and an empty value clears it; the title cannot be cleared. Changing the acceptance criteria (their text, order or entries) withdraws every review and open or claimed review request of the old ones, an acceptance (the task goes back to claimed) and an override of the review, and says so: a fresh review of the new criteria is needed. The same list again changes nothing. The criteria of an integrated part, or of a task being landed, cannot change.
atelier ls [--all] [--json]
Lists the project's tasks with state, owner and head. Merged and abandoned tasks need --all. --json prints them for scripts, each task with its created, updated and last-push times, as Observatory reads them.
atelier show ID [--reviews] [--json]
Prints a task's decision brief: what is decided, the recorded evidence, a recommendation and the task's address. --reviews also prints each review at each head with its whole note and findings; --json prints the brief, carrying the reviews, for scripts.
atelier receipt ID [--json]
Prints one task's whole story from the ledger, in the order it was recorded: created, claimed, each handoff and release, each pushed head as Artifacts answered it, each observed check at each head, each review with its verdict and every finding with the owner's verdict on it (confirmed, refuted or fixed), then the submission, acceptance and merge or abandonment that ended it. --json prints the task's events as the ledger holds them, in order.
atelier owners [--json]
Prints one line per live task: its state, its owner and since when.
atelier inbox [--json]
Prints the decision brief of each task that needs the project owner, most urgent first. --json prints the entries for scripts.
atelier status [--project P] [--json]
Prints the owner's queue for every project: what waits for the owner, which pairs of live tasks name overlapping scopes (each pair once, nothing waiting on the owner), what is in progress and what waits for a runner, with the live item each dispatch the project's core files hold waits on, each open review request among it with its reviewer named, and, for any queued job no live runner offers, that it can never be claimed until a runner that offers it is started, which is a mismatch between the dispatch and the runners rather than a wait. With --project it prints where one project stands instead, ending with whether this checkout is in step with the baseline and, when any of the project's tasks has a workspace on this Mac, an On this Mac section: each live task's workspace with its uncommitted changes, commits not pushed to its fork, a merge in progress and a waiting COMMIT_MSG.txt, a count of the merged or abandoned tasks' workspaces left behind, and whether a landing is running here for the project. --json prints machine-readable records, each task with its created, updated and last-push times, as Observatory reads them, the overlapping pairs under overlaps and the same local facts under local.
atelier open
Opens the server in a browser, using the macOS open command.
Agents
atelier start ID [--as H/M] [--runner home:NAME]
Claims the task, prepares its workspace as claim does, and prints its title, brief, acceptance criteria, scope and any dispatch note. --runner names the runner when a runner claims a dispatched task.
atelier done "summary" [--sandbox]
Pushes, runs the required checks and submits, in that order, and stops at the first step that fails, naming it. --sandbox runs the checks in a Cloudflare container. Its last line says Ready for the owner or what still blocks the task.
atelier claim ID --as H/M [--runner home:NAME]
Takes ownership of a task, forks the baseline into the task's workspace, mints a write token for the claimant alone, clones the workspace and records the project's branch as the one it pushes to. Claiming again refreshes the token and that branch, saying when the branch changed. --runner names the runner when a runner claims a dispatched task.
atelier finish [--sandbox] [--summary T]
Run in the claimed workspace: pushes, runs the required checks and submits, only if they pass and the workspace has not changed meanwhile. --sandbox runs the checks in a Cloudflare container. done is finish with a required summary.
atelier push [--force | --rollback]
Pushes the workspace to the task's fork, then asks the Worker to read the head from Artifacts. The ledger records the head Atelier saw, not the one the agent named. It refuses, pushing nothing, when the workspace's branch is not the one the fork's HEAD names, since Atelier reads only that one. After update, --force pushes with a lease. --rollback returns the fork to an earlier commit of the recorded history, as the plan integrator does after a failed integration.
atelier update
Rebases the workspace onto whatever has merged to the baseline since the fork, then names the next step, atelier push --force, whose lease refuses to overwrite anything pushed since the workspace last fetched.
atelier check [--sandbox] [--merged] [-- CMD]
Runs each required check, or the command after --, in a clean clone of exactly the head Artifacts holds, measures which paths changed since the baseline, and records each result as Observed. --sandbox runs them in a Cloudflare container instead. --merged runs them on the would-be merge, the head merged with main as main is now, in a temporary merge commit that is never pushed; the result is recorded against both revisions, shown beside the merge preview, and goes stale when either moves. A local check runs with the caller's file access, so it can read their files and Keychain and reach the network; it is given only the environment variables toolchains need, and Atelier's tokens are redacted from its output before upload. Run untrusted code with --sandbox.
atelier report [ID] "what you verified and how" [--item ID] [--project P]
Records a Reported claim at the current head: what the agent verified and how. It goes on the task named, else on the workspace's task; in a workspace, another task's id needs --item ID. It is shown and never counted as a check.
atelier submit [--summary T]
Marks the task ready for the owner and prints what still blocks it, if anything. --summary stores a summary of the change with the submission.
atelier handoff ID --to H/M [--note TEXT]
Moves ownership to another agent, with --note saying why. The old write token is revoked; the workspace and its history carry over.
atelier release ID [--note TEXT]
Gives the task up: it returns to open and the write token is revoked.
atelier block [ID] "what it is waiting on"
The holder or the project owner blocks the task with what it is waiting on. It keeps its owner and workspace, leaves the runner queue and stuck detection, cannot be pushed, submitted, reviewed, handed off or released, and sits in the owner's inbox with the reason until it is unblocked.
atelier unblock [ID]
The holder or the project owner lifts the block, and the task returns to the state it was in.
atelier diff ID
For a reviewer: prints the task's commits and diff against the baseline, from a clean read-only clone.
atelier review ID --approve|--reject --criteria BINDING [--request N] [--note TEXT] [--head SHA] [--findings JSON]
Records a verdict on the task's current head, with --note giving the reason. --head names the revision the verdict is for, and the server refuses one for any head but the current. --criteria names the binding of the acceptance criteria the verdict judged, as atelier show or the review claim gives it; the server refuses a verdict that names none, or criteria the task no longer has, and the reviewer must read them again. --request names the review request the reviewer claimed, which the verdict then answers alone. --findings attaches a reviewer's structured findings. The rules say whose approval counts.
atelier review-claim ID [--runner home:NAME]
A reviewer's runner claims the task's open review request and gets the part, its brief's inputs, the binding of the acceptance criteria the brief carries and the request's number, which the verdict names, and a read token for its fork.
atelier review-release ID [--note T]
A reviewer whose harness wrote no valid verdict lets the review request go, so another reviewer may take it.
atelier read-token ID
Reads a token for the task's own fork, with its head and base, for a job that clones it outside a task or a review.
atelier base-token ID
Reads a token for the repository the task is measured against: the plan's fork for a part, the baseline otherwise.
atelier integrated ID --part KEY --merge-commit SHA
The integrator reports a verified merge of one part onto the plan's branch; the server checks the commit against the branch before recording it.
atelier integration-failed ID --part KEY --reason TEXT [--kind conflict|checks]
The integrator reports a failed merge, which sends the part back to its builder for rework with the reason. --kind says the failure was the part's own, a merge conflict or failing checks, which charges its builder an attempt; without it the builder is charged nothing.
atelier refreshed ID --main-head SHA [--merge-commit SHA]
The integrator reports a refresh: main's head, the one the refresh job names, merged into the plan's branch. The server checks the merge commit against the branch before recording it, and it becomes the commit later parts fork from and later integrations build on. Without --merge-commit the branch already held main's head, which the server checks.
atelier refresh-failed ID --main-head SHA --reason TEXT [--kind conflict|checks]
The integrator reports a refresh that conflicted or failed the plan's checks, after rolling the branch back. It is recorded on the plan with the reason and charges no part's builder; the tick does not try it again for that main head.
Owner
atelier accept ID [--head SHA] [--override-review REASON] [--note TEXT]
The project owner accepts the task at its current head; --head names that head, and any other is refused. --note keeps the owner's word on the acceptance with it in the ledger. It is refused unless the gate is clear. When the change still lacks its independent review because no reviewer qualifies, --override-review overrides that review and accepts: the reason is required, the override is recorded as an event of its own, never as a review, and the task page and the inbox show it with its reason.
atelier merge ID [--head SHA [--approve [--note TEXT]] [--override-review REASON]] [--policy-changed-ok]
The project owner lands the accepted head in the registered checkout and publishes the merge to the baseline. With --head, a submitted task is accepted at that exact revision first: --approve records the owner's review, with --note as its reason, which is not the independent review, and --override-review accepts with the owner's override, as accept does. Run again, it resumes an interrupted merge; --cancel ends one. A plan whose branch would conflict with main is not accepted, and one accepted that conflicts at the merge is put back to building through plan refresh, which takes main into its branch.
atelier merge ID --cancel [--discard-local]
Ends an interrupted merge: the landing lease is released, so the task's owner can push again. An unpublished merge commit in the checkout is kept unless --discard-local removes it and returns the branch to where the merge began.
atelier land ID [--reviewer H/M] [--no-review] [--wait] [--dry-run] [--release-lease] [--workflow [--checks local|container]]
The project owner lands one task whole; a plan is refused before the lease, since it lands with atelier merge ID --head H at the integration head plan show prints and takes main through plan refresh. It takes the project's landing lease on the server, so two sessions never race main, then merges main into the task's workspace, stopping on conflicts and leaving them for the owner, naming the files. It regenerates the project's fixtures when the policy declares how (init --regenerate), pushes, runs the required checks and submits. It requests the independent review the gate needs through the review-request routes and waits for the verdict (up to 60 minutes, or ATELIER_LAND_REVIEW_TIMEOUT milliseconds), saying what the runners are busy with while the request is unclaimed, and, when no live runner offers the reviewer for the review job, that the request can never be claimed until one does, with the review by hand and the --reviewer that asks a model a runner offers, then accepts and merges. A named --reviewer is always asked, even where the gate needs no review, and a rejection stops the landing. Each step, its duration and the commits that came from main are recorded as land.* events, for the integration record. It refuses to start when the server's route level is lower than this CLI's, saying to deploy, and while another task's landing holds the lease, naming who holds it and since when, unless --wait queues behind it: the server keeps the landings waiting for the lease in the order they queued and hands it to the first of them when it frees, so one landing cannot take it ahead of another that waited longer, however their polls land. While it waits the landing asks the server again on every poll, says whose landing it waits behind and which landings are queued ahead, and starts when its turn comes (three hours at most, or ATELIER_LAND_WAIT_TIMEOUT milliseconds), so several landings started at once run in turn; a landing that stops asking drops out of the queue once 15 minutes pass without an ask. The lease is renewed while the landing runs, released on SIGINT or SIGTERM, and treated as free by the server once 15 minutes pass without a renewal, which the next landing reports when it takes the lease over. --reviewer names the reviewer; --no-review leaves the task submitted; --wait queues for the lease, in the order the landings queued; --dry-run prints the steps and the refusals without changing anything; --release-lease frees the project's lease, saying which task held it since when. --workflow lands through a Cloudflare Workflow instead: the lease, the submission, the review wait and the acceptance run on the server as durable steps, each retried through transient failures (an Artifacts 503, a lost connection) and needing no token, while this command shows the Workflow's stage and does the steps that need Git with a working tree when the Workflow asks: merging main and pushing (only once the Workflow holds the lease), and atelier merge once it has accepted. --checks says where the required checks run: local (the default) runs them on this machine in a clean clone of the pushed head before the head is reported, as the plain landing does, and the Workflow goes on only once the server has recorded every one passing, observed, at that head (failing ends the landing; no result within 30 minutes does too); container has the Workflow run them in a Cloudflare container, for a project whose suite finishes there. A conflict pauses the Workflow with the lease released and the files named; resolve and commit them, then run the same command again to resume. If the command stops (a closed laptop), the Workflow keeps its place: run it again to attach. --dry-run and --workflow together are refused.
atelier abandon ID [--note TEXT] [--delivered-by tN]
Closes the task without merging it. The holder's write token is revoked; the history and evidence stay. --note says why. --delivered-by records that the merged task tN delivered it, for work another task already brought in.
atelier defect ID --note TEXT [--found-in ID]
The project owner traces a defect to the revision the task was accepted at. Nothing about the task changes; the reliability record counts the defect against the model that built that revision and against each model that approved it. --found-in names the task the defect was found or fixed in.
atelier finding ID --head SHA --index N --verdict confirmed|refuted|fixed [--note TEXT]
The project owner records a verdict on one finding of a review: confirmed, that the finding was right and a fix followed; fixed, that it was right and is fixed; refuted, that it was wrong. --head names the review's revision and --index the finding's position in that review's findings, one based. The event is the record, and the reliability record counts the reviewer's findings confirmed and refuted, which measures its precision.
atelier run-report --actor H/M --role build|review --outcome KIND [--project P] [--item ID] [--detail TEXT]
The project owner records a run that ended without a result the ledger saw, for a run outside the runner: an early stop, a permission stop, a duplicate design or an incomplete merge, beside stalled, timed-out and refused, which the runner reports itself. The reliability record counts it against the actor, and a review run counts as a review that never reached a verdict.
atelier served MODEL --recorded H/M --from TIME --to TIME [--item ID]... [--note T] [--apply]
The project owner records which model served events recorded under another, as when zcode served deepseek-flash while its events named glm-5.3. Each event recorded as --recorded from --from up to --to, on the tasks --item names or on every task, gets an annotation of its own, and the track record, the reliability record and the graph count it under the served model; the event itself never changes. Without --apply it lists the matches and records nothing. --note says how the owner knows.
atelier approve ACTION --head SHA [--note T] [--expires 24h]
The project owner approves one protected action, such as deploy, install, paid-run or photos-writeback, at one exact revision of the main line: the full SHA of a commit the baseline holds. atelier ship uses the approval once, at that revision only, and a later revision needs its own. It stands for 24 hours unless --expires gives from 1m to 30d; --note records why. Any other kind must be one the project's ship files name.
atelier approvals [--all]
Lists the approvals that stand, each with its kind, revision and expiry. --all adds the used, withdrawn and expired ones.
atelier approvals withdraw ID [--note T]
The project owner withdraws an approval no ship has used, so none can use it.
atelier ship [--dry-run] [--push]
Run by the project owner in the registered checkout, clean and at the baseline's head: composes the ship order from the project's ControlPlane ship policy and adapter, or from docs/atelier/ship.json, and refuses before running anything when a protected step has no approval at that revision, naming the command that approves it. It then runs the steps in order and stops at the first that fails, recording each step's command, exit status, duration and redacted output tail on the ledger. It pushes only with --push, which needs no approval since ship is owner-only and runs at one exact revision, and never forces a push. --dry-run prints the steps and which approvals are present or missing, and runs nothing.
Plans
atelier plan "goal" [--scope GLOB]... [--planner H/M]
The project owner states a goal. Atelier creates the plan task and queues it as a plan job for the planner named, or else for the first model in the pool for research work that is not refused, not paid per token and may plan. A project has one active plan at a time. A runner that offers plan jobs takes it: the planner claims the plan task, reads its brief from the job-brief route and posts the plan document the harness wrote; by hand, a planner claims with --runner and runs plan post.
atelier plan show ID [--json]
Prints a plan: its phase, the newest proposal with its hash, or once approved each part with its state, dependencies, scope, routing and attempts, the live item outside the plan a queued part waits on while the project's core files hold it, any live review request naming the reviewer asked and whether a runner claimed it, and saying, when no live runner offers that reviewer for the review job, that the request can never be claimed until one does, with the reroute that names another, then the part dispatches used, why it is blocked, the main head the branch last took against main's head now with any refresh in flight or failed, and the command for each decision waiting on the owner. Before approval it shows the routing an approval would fix now, its reviewers judged against the live runner offers the same way. It accepts a part's id too.
atelier plan approve ID --hash HASH [--allow-paid]
Approves the split, once, by the hash of its newest proposal; an older hash is refused. The routing of each part is fixed then, with the limits: 2 parts live at once, 3 attempts a part, 4 dispatches a part, 24 hours. A part that no model can build, or that no model of another family can review, refuses the approval; a reviewer counts only when a live runner offers it for the review job, and when no runner is live the pool stands, with the routing saying so. --allow-paid lets models paid per token build and review.
atelier plan revise ID --note TEXT
Before approval, sends the plan back to its planner with a note; its next proposal replaces the one before.
atelier plan reroute ID --to H/M
Names who builds an open part from now on, its attempts counted afresh; for a submitted part, or one blocked for want of an eligible reviewer, names its reviewer, in the pool or not, which must be of another family than every contributor; before approval, names another planner for the plan.
atelier plan retry ID
Counts an open part's attempts afresh, so its builder is asked again; before approval, asks the planner again.
atelier plan refresh ID [--resolve [--to H/M]]
Queues the plan's refresh job for the integrator, which merges main's head into the plan's branch, so later parts fork from it; parts wait for it before they are dispatched. The tick does this itself before it dispatches a part when main has moved, once per main head; this runs it again, as after a failed refresh. On a plan submitted or accepted, it first withdraws the submission and any acceptance and puts the plan back to building, and says so; the integrator submits it again once every part is integrated on a branch that holds main. It is refused before approval, once the plan is closed, while the plan's integrate or refresh job is queued or held, while a merge of the plan holds its landing lease, and when the branch already holds main's head. A refresh that conflicts adds a merge-main part to the plan, which a model builds: the runner merges main into the part's workspace and leaves the conflicts for it to resolve, and its integration puts main on the branch; no other part is dispatched until it is integrated. --resolve adds that part for main's head now without trying a refresh first, built by --to when named; it is refused while a refresh is queued, when the part for that head exists, while another merge-main part is not integrated, and when the branch already holds main's head.
atelier plan stop ID [--note TEXT]
Closes the plan and every part not yet merged, revoking their write tokens. The history and evidence stay.
atelier plan post ID FILE
The holder of the plan task's claim, its planner, posts the plan document in FILE. An invalid one is refused with every error, and the planner gets one more attempt before the plan blocks.
Models
atelier models
Lists the model pool: each model's harness, where it runs, its family and what a runner last found.
atelier models add ID --harness H --where home|cloud [--provider P] [--endpoint URL] [--keychain NAME] [--alias A]... [--note TEXT]
Adds or replaces a pool entry. Atelier never stores a key: --keychain names the Keychain entry that holds it, and a request that carries a key is refused. --note keeps a note with the entry.
atelier models remove ID
Removes a model from the pool.
atelier dispatch ID [--to home|cloud|any] [--agent A] [--model M] [--note T] [--job merge-main [--head H]] [--overlap-ok]
Queues an open task for a kind of runner, and optionally an agent and model, instead of waiting for an agent to choose it; a held task is released and queued in the same step, keeping its workspace and commits. While the task's scope overlaps, within one of the project's core files (init --core), the scope of a live item, the queue holds it and offers it to no runner until that item merges or is abandoned; atelier status and atelier queue say which item it waits on, and --overlap-ok lets this dispatch through at once. --job merge-main sends a task whose landing conflicted with main back to its builder: the runner merges main at the named head into its workspace (main's head as the baseline holds it, unless --head names one) and leaves the conflicts for the builder to resolve and commit; then atelier land ID again. Project owner only.
atelier undispatch ID
Takes the task out of the queue.
atelier queue
Lists everything waiting for a runner, across projects, oldest first, and for each dispatch the project's core files hold, the live item it waits on.
Projects
atelier projects rename OLD NEW
The project owner gives a project a new name on the server, and this machine's config entry moves to it. The ledger, the baseline repository and every fork stay where they are. The old name keeps working: the API serves it, old page links redirect, and tokens and workspaces that use it need no change. A name another project has or had, or one a removed project's ledger is kept under, is refused.
atelier projects remove NAME [--force]
Removes a project from the index and from this machine's config. The Artifacts repository and the ledger are kept. It is refused while work is live unless --force is given.
atelier showcase set NAME [--named|--anonymous]
The project owner adds a project to the public showcase. Anonymous is the default: the project's card and its task stories carry a neutral label from the project's kind, never its name, a task title, a path, a commit message or an address. --named shows the project by name. Nothing is public until this is run.
atelier showcase remove NAME
Takes the project off the public showcase. With no subcommand, showcase lists what is shown and how.
atelier init --name NAME --rename-local
Changes only this machine's local name for the registered checkout. Nothing on the server changes.
atelier adopt --project NAME [--as H/M]
Moves a ControlPlane project to Atelier as an ordinary task: claims it and, in its workspace, writes bin/control-plane, inserts the text atelier guide prints into AGENTS.md and commits without pushing. It then lists what the finishing agent must settle.
Local
atelier gc [--project NAME] [--dry-run | --apply]
Previews the local workspace and check clones that are safe to remove; --apply removes them. It never touches Artifacts or the project checkout.
atelier runner --name home:NAME [--once] [--config PATH] [--integrate]
The home runner: polls the queue every 30 seconds, claims one eligible task and runs its configured harness in the claimed workspace. Each opencode run gets a data folder of its own beside the workspace, removed when the run ends, because opencode runs that share one deadlock on its database. When the harness commits, the runner runs finish. --integrate runs no harness: it offers only the integrate and refresh jobs and merges each part onto its plan's branch as atelier/integrator. --once handles at most one task.
atelier runner --discover [--name home:NAME] [--probe] [--dry-run] [--config PATH]
Reports which model each home harness actually served, from the records the harness keeps, and sends the result to the server as each model's status. --probe also sends one short prompt to each model that can be probed; --dry-run reports nothing.
atelier runner --usage [--name home:NAME] [--dry-run] [--config PATH]
Reports how much of each tool's allowance this machine has used: Codex's 5-hour and weekly windows, the requests and tokens zcode and opencode recorded by served model over the last 5 hours, 24 hours and 7 days (with cost, for opencode), and the DeepSeek balance when the runner config names its Keychain entry. After the tools' own figures it prints what the server read of the AI Gateway: each model's calls, tokens, cost and durations, and the calls per task the runners' cf-aig-metadata tags name; then each model's speed over the last 14 days (median build, review and claim-to-merge times with n, and the share of runs that stalled). Each tool's summary goes to the server under the runner's name, for the Usage page and its alerts; --dry-run reports nothing. It runs once, not as part of the runner loop. Claude's plan limits and Gemini's spend have no record on the machine and are not reported.
Ops
atelier ops COMMAND [ARGS...]
Hands everything after ops to the private atelier-ops toolkit, named by ATELIER_OPS or found on PATH. Without one it says so and exits 2.
Docs
atelier guide [--role build|review|plan|orchestrate]
Prints the instructions an agent needs, to paste into a project's AGENTS.md or CLAUDE.md. --role prints the instructions for one role alone, from a project's .atelier/prompts/ROLE.md when it has one. atelier adopt inserts the plain guide.
Tokens
atelier token issue --as H/M [--project P]... [--days N] [--label TEXT]
The project owner issues a token bound to one actor and shown once. It expires in 30 days unless --days (1 to 365) says otherwise, and covers the named projects or all of them.
atelier token ls
Lists token records without the tokens or their hashes.
atelier token revoke ID
Revokes a token; later API requests with it are refused. Git credentials already issued keep their own lifetime.

Agent instructions

The text atelier guide prints
## Working through Atelier

Several agents may work on this project at once. Each piece of work is a
task with exactly one owner. Never edit the project checkout directly.

1. `atelier start ID --project NAME --as HARNESS/MODEL` claims the task
   and prints its workspace, title, brief, acceptance criteria, scope and
   note. Work only there; a change that fails a criterion is rejected.
2. Commit your changes, then run `atelier done "summary"` in that workspace.
   It pushes, runs required checks and submits only after they pass. Relay
   its final line to the owner. The project owner accepts and merges.
3. `atelier inbox` and `atelier show ID` print briefs you can relay to the owner.
4. `atelier ls --project NAME` lists tasks. Ask the owner to create one if needed.
5. Individual steps remain available: `atelier claim`, `atelier push`,
   `atelier check` and `atelier submit --summary "summary"`.
   `atelier report "…"` records a Reported claim, never an Observed pass.
6. If you can't finish, `atelier handoff ID --to HARNESS/MODEL --note "…"`
   or `atelier release ID`. Your write token is revoked either way.
   Waiting on something only the owner can settle: `atelier block ID "what"`.
   The owner sees the reason in the inbox and runs `atelier unblock ID`.
7. Reviewing someone else's task: `atelier diff ID`, then
   `atelier review ID --approve|--reject --note "…"`. Changes to protected
   paths need approval from a model of another family than every agent
   that worked on the task.
8. `atelier update` rebases your workspace onto whatever has merged since.

For each session the project owner runs in the registered checkout:

1. Start a session with `atelier unwrap --project NAME`; relay its short paragraph.
2. End with `atelier wrap "summary" --next "what is next"` in the registered checkout. It runs the registered checks, commits, and always updates Atelier's own copy of the project, the baseline. A failing check stops it before anything is committed: fix the check, or add `--allow-failing` to commit anyway and record in the note which checks failed. It never pushes the project's own remotes unless `--push` is given; add `--push` only with the owner's approval for that session. It never deploys or publishes a release.
3. Before the session closes, file a defect in Atelier or project tooling as a task in its project: atelier new "…" --project NAME. For Atelier use --project atelier. File a lesson worth keeping the same way with a title starting "Lesson: ". Use repeatable `--found TEXT` on wrap to file tasks in this project.

A protected action (a deploy, a device install, a paid model run or a Photos
writeback) runs only with the project owner's approval for one exact revision
of the main line, given with `atelier approve KIND --head SHA` and used once.
`atelier ship`, run in the registered checkout when the owner asks, runs the
project's ship order, uses those approvals and records every step; its
`--push` pushes the branch to the project's own remotes and needs no
approval, being the owner's own act at that exact revision. Never approve an
action for the owner, and never run one without the owner's approval at that
revision.

Session notes keep metadata only, never prompts, transcripts or file contents.
Material for the owner to copy is one complete fenced block with a language
tag: bash for a command the owner runs, text for prose, a brief or an envelope.
Never leave prose the owner must select by hand. Save a copy under
~/Documents/ai-project-data/<project>/, never the portfolio root.