Restructure the agentic pipeline into task skills and orchestrators - #842
Merged
Jack-Edwards merged 11 commits intoAug 5, 2026
Merged
Conversation
The plan now comes from /crypter-plan-author, a skill an interactive session runs on the host, where the web, the user's tooling and the user are all reachable. Compose mounts .claude/plans read-only at /plans, so the container reads the plan where it was written and keeps its own run state in the workspace. /pipeline takes a run id and a branch and starts at the sync, with the host session driving it once the plan is approved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The container ran with a fork-scoped PAT and unrestricted egress, since Claude Code needs the Anthropic API. Rather than harden egress, the credential is gone: the workspace is now an anonymous clone of the org repository whose only remote has no push url, so the agents read public code and commit locally. Pushing, opening the pull request and the CI loop move to the host, where the session already has GitHub access. /crypter-publish fetches the branch out of the container over git's ext transport, pushes it, opens the draft pull request, and runs at most three fix attempts, handing each failure to /pipeline-fix through the read-only plans mount. CRYPTER_FORK and CRYPTER_FORK_TOKEN leave .env, and the image drops the GitHub CLI, which has nothing left to authenticate with. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One skill drafted a plan and launched a container, and another implemented, reviewed, triaged and remediated. Neither name described what it did, and neither part could be used alone. The work now sits in small skills that compose: crypter-plan, crypter-implement, crypter-examine, crypter-remediate and crypter-publish. crypter-change carries a requirement through all of them and owns the CI loop, where the plan, the findings and every previous attempt are already in context. crypter-review runs the same lenses against a pull request that already exists, including one raised by someone else, and reports without posting anything. Findings become artifacts on the host. A second mount carries .claude/runs into the container, and each reviewing agent writes its own file there, so what was found and what was rejected can be read without docker exec. The orchestrators create those directories because the container's agent is uid 1001 and host files are uid 1000. The reviewer's lens table grows from one row to correctness, maintainability, testability and security. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
crypter-examine claimed the branch it reviewed, so it failed whenever anything else already held that ref. It only reads, and a detached worktree reads just as well. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pipeline reviewed its own branch. Acting on what it raised: crypter-publish becomes crypter-open-pull-request, since the skill only ever opens a draft and taking a pull request out of draft is the user's call. ci-watcher loses the fork parameter it was never given and resolves the repository from the path it already has, and reads the branch's version of a file rather than whatever the working tree is checked out at. crypter-review forces its fetch refspec, so a pull request that was rebased or amended can be reviewed again. The mount table named the run-id directory as the mount source; the mounts are the parents. The pre-flight check tested the parent of /runs, which uid 1001 cannot write to by design, so it now tests the run directory the caller just created. The launch steps create both mount sources, because Docker creates a missing bind-mount source as root. The documentation credited a fork-scoped token with bounding the container and claimed it holds no credential; the Claude Code credential is still there, so it says GitHub credential and names the container as a trust boundary. The orchestrators state that they run from the main checkout, which is where the mounts resolve.
The agents run unattended, which is the whole reason permissions had to be resolved without a prompt. Auto mode does that without handing them the bypass, and the unprivileged user in the image stands on its own merits rather than on what the bypass refuses to do as root.
.claude/runs is a writable mount into the pipeline container, and /crypter-review runs agents over diffs written by people outside this project. A symlink left in that directory resolves on the host side, so reading a findings file can read any file the user can, and writing triage.md can overwrite one. The hook blocks a path inside the project that resolves outside it. Paths that point outside to begin with are untouched, since asking for one is deliberate. It is written in Node because the build already depends on it, so it runs where a shell script would not.
A review that lands only on the reviewer's disk asks the reviewer to retype it. crypter-review now triages what the lenses raised and posts a single review that comments, with a line comment for each finding that lands inside the diff and the rest in the body. It never approves and never requests changes. The pull request may belong to someone else, and either verdict is the user's to give. Triage comes first, and it is written to triage.md, so the findings that reach the author are the ones that survived a read against the code, and so a later remediation run has something to start from.
A finding on a pull request is a claim, and acting on a wrong one means changing working code. crypter-triage-review gives each finding its own verifier with the code and nothing else to judge it by, so no agent ever rules on a finding it raised. What holds becomes a commit. What does not gets a reply on its thread carrying the evidence, so the person who raised it can answer. What the verifier cannot settle goes to the user untouched. The commits go back to the pull request's own branch, which is only possible when it comes from a repository the session can push to. Otherwise the replies stand on their own and the author does the fixing.
The skills are visible wherever the repository is checked out, and four of them only work inside the pipeline container, where /work/Crypter, /plans and /runs exist. The prefix says which is which, so the skills a user invokes are the ones without it.
The two skills a session runs on behalf of an orchestrator now say so in their names. crypter-step- rather than crypter-change-step- because open-pull-request is a step of crypter-triage-review as well. What is left unprefixed is what a user invokes: crypter-change, crypter-review, crypter-triage-review.
Jack-Edwards
added a commit
that referenced
this pull request
Aug 6, 2026
* Build and review in per-run container workspaces The container kept a long-lived clone of the org repository in a named volume. Nothing ever advanced its working tree, so the pipeline's own skills and agents were frozen at whatever commit the volume was created with. The rename in #842 made that visible: the host began invoking crypter-devcontainer-implement while the container still only knew crypter-implement, and both orchestrators broke. Workspaces are now created per run at /work/{run-id} and deleted when the run ends, cloned from a read-only mount of the host's .git. A copy of the code no longer outlives the run that made it, so it cannot drift. The mount being read-only is what makes sharing history safe: the container reads commits and cannot move a ref or add an object. The host working tree is not mounted, so uncommitted work stays invisible to the agents. The orchestrator owns the lifecycle and the container skills assume a workspace exists, mirroring how /runs already works. This also retires the worktree add/remove pairing in every container skill, along with the stale worktree a crashed run used to leave behind. The image is built locally instead of pulled from GHCR. It carries tooling and no source, so a change to workspace.sh or the Dockerfile is a rebuild rather than a publish waiting on an approval. NuGet and pnpm caches move to named volumes. Workspaces are ephemeral, so without them every run would restore from nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Stop publishing the devcontainer image The image is built locally and carries tooling rather than source, so nothing consumes the published copy. Publishing it on every merge to stable left a registry image nobody pulled and an approval gate on changes that only ever affect the machine making them. pr-build-devcontainer stays. It pushes nothing and catches a Dockerfile that does not build, which is the only part of the publish path that was earning its keep. The devcontainer environment on the repository has no jobs left referencing it and can be deleted from the repository settings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: n <e@f> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A change goes through orchestrators composing a set of task skills. The three skills without a prefix are the ones a user invokes;
crypter-step-runs in your session on an orchestrator's behalf, andcrypter-devcontainer-runs inside the pipeline container./crypter-change "<requirement>"/crypter-review {pr-number}/crypter-triage-review {pr-number}/crypter-step-plan/crypter-devcontainer-implement/crypter-devcontainer-examine/crypter-devcontainer-verify/crypter-devcontainer-remediate/crypter-step-open-pull-requestEvery skill that reads or writes code runs in the container, including both review phases and verification. The session that invokes an orchestrator plans, triages, and talks to GitHub.
The container holds no GitHub credential. Its workspace is an anonymous clone of the org repository with one remote,
upstream, whose push url is cleared, so the agents read public code and commit locally./crypter-changeowns the CI loop and runs at most three fix attempts. The pull request it opens stays a draft.Two mounts carry everything across the boundary:
.claude/plansread-only for the plan, and.claude/runswritable so each reviewing agent writes its own findings where you can read them. The branch itself travels over git'sexttransport ondocker exec.Notes for review:
/crypter-reviewposts one review with eventCOMMENT, after triaging the lenses against the code and recording what it kept and dropped intriage.md. It never approves and never requests changes./crypter-triage-reviewreads the findings already on a pull request, whoever left them, and gives each one its ownfinding-verifierwith the code and nothing else to judge it by. A finding that does not hold gets one reply on its thread. A finding that holds becomes a commit on the pull request's own branch, which needs a head repository the session can push to. It watches no CI..claude/settings.jsonis new to the repository. It registers aPreToolUsehook that refuses a file operation on a path inside the project resolving outside it, which is how a symlink written into.claude/runswould reach the rest of your machine. Claude Code asks each contributor to approve the hook the first time they see it.claude --permission-mode auto.CRYPTER_FORKandCRYPTER_FORK_TOKENare gone from.env; only the two git identity variables remain. Revoke the token once this is merged..devcontainer/clone-fork.shis renamed toclone-upstream.sh, and the image drops the GitHub CLI..claude/agents/plan-author.mdis deleted; its guidance is now/crypter-step-plan.ci-watcherruns in your session and reads checks through the GitHub MCP server, orghwhere it exists.reviewerlens table grows from one row to correctness, maintainability, testability and security. The agent definition is unchanged; lenses come from the prompt..claude/runswith mode 777. The container'sagentis uid 1001 because the base image already has a user on uid 1000, and a bind mount keeps host ownership. Directories created host-side stay deletable host-side..claude/plansand.claude/runsbefore the firstup. They are gitignored, and Docker creates a missing bind-mount source as root.stablequeues the approval-gated publish workflow. Recreate any existing container afterwards.originremote pointing at the fork. Without a credential those pushes fail, and a fresh workspace has onlyupstream.