Skip to content

agents: architecture guides per area + pstack factory glue - #1292

Draft
alexanderludwig wants to merge 24 commits into
codex/sandbox-stackfrom
tooling/claude-factory
Draft

alexanderludwig wants to merge 24 commits into
codex/sandbox-stackfrom
tooling/claude-factory

Conversation

@alexanderludwig

@alexanderludwig alexanderludwig commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

What this is

A local "factory" that takes one GitHub issue (or task file) to a branch ready for a PR. It's pstack 0.15.25 run as is, plus a thin layer of AntSeed glue. Everything sits under .factory/.

The base is codex/sandbox-stack (#1145), so this diff shows only the factory. #1145 keeps only the sandbox and the verify-antseed skill (with prove.sh).

This PR is the main "make the repo agent-ready" change. It has the architecture knowledge, which any agent reads (factory or not), and the factory glue.

Architecture knowledge

File Purpose
root CLAUDE.md → ## Design principles Credibly neutral: use existing standards and keep them intact, extend through plugins and policies, keep hosts thin (desktop and gateway changes go through the CLI, not the core), keep money stable and logic swappable.
root CLAUDE.md → ## Invariants 5 rules for the whole repo, each with what enforces it. Rules for one area live in that area's guide.
11 area guides: packages/{node,buyer-core,protocol,api-adapter,router-core,web-sdk,contracts}, plugins, apps/{cli,desktop}, apps/cli/src/gateway Short and not standalone: one paragraph on what the area is and why, a rules table with what enforces each rule, and an Ask-first line. Nothing from the root is repeated.

Factory glue (.factory/)

File Purpose
start.sh Makes the worktree, the task file, the orch store and the session settings (model pin to the AntSeed buyer, no push, Bash sandbox, deny rules), then launches claude with pstack and the plugin. FACTORY_RESUME=<id> reopens a session.
RUN.md The session's first message: the task, the exit condition, where AntSeed's files are, and how pstack's Cursor names map to Claude Code.
orchestrator.md Our dispatcher. It replaces poteto-mode's Playbooks section: it routes the task to a pstack playbook (an architecture change goes to figure-it-out), adds overlays for area rules, high blast radius and user-visible changes, adds AntSeed steps to named playbook steps, and handles verification and done: one swarm at the code-ready head and a single verifier per later head (Autopilot-full step 4). Findings are sorted with interrogate's buckets: one that is no worse than main is never Act on. Plans name the smallest change (principle-laziness-protocol), a second writer to shared state needs a reason, and repeated fix rounds on one premise trigger principle-attack-the-premise and a scope gate. It also sets how subagents are briefed: Orchestrate's brief template, standing orders in orch/preferences.md (seeded by start.sh), short reports, a lead that keeps only summaries and checkpoints with Pause safely.
root CLAUDE.md rewrite About 75 lines, down from about 270: working rules, principles, invariants, an area map, the payment flow, commands. The old local-testing recipe is replaced by pnpm sandbox.
add-migration skill Lists the third migration domain, verification.
pstack-models.md pstack's model table per role: opus writes code, astra verifies, review panels mix opus, fable, astra and sol.
sandbox-run.sh Runs one --chain local sandbox scenario outside Claude Code's Bash sandbox (sandbox.excludedCommands), because forge panics inside it. Refuses --live, --real-funds and mainnet.
plugin/agents/* astra and sol (model pins) plus read-only reviewer versions.
git-hooks/pre-push, check-push-gap.sh Block pushes from the session, and prove the known routes are refused.

Process

The playbook steps are pstack's, referenced and never copied. The dispatcher (orchestrator.md) is ours. See .factory/README.md → "The process: pstack's steps, our dispatcher".

Tried on

Not a user-facing change

This is dev tooling and agent docs only, so there's no CHANGELOG entry.

Known limits

See .factory/README.md → Known limits. localhost stays reachable, the no-push env is a speed bump, and the model pin is set once per session.

@alexanderludwig alexanderludwig changed the title tooling(factory): pstack-based issue-to-PR factory (glue only) agents: architecture guides per area + pstack factory glue Oct 20, 2026
@alexanderludwig
alexanderludwig force-pushed the tooling/claude-factory branch 4 times, most recently from bff0c1f to 5a2cd42 Compare October 21, 2026 03:20
factory and others added 24 commits October 21, 2026 08:52
…e import

Every judgment step is a fresh Claude Code session pinned to GesundAI through
the audit proxy, in its own Herdr pane; every gate is a script. Stages:
intake (sharpen into a test plan), red, fix, verify (verify-antseed table as
data, doctor first, sandbox before/after in a scratch worktree), blast radius,
three-model review (opus-5.5, fable-5.1, gpt-6-astra) plus judge, no-comments,
publish (dry run only), reflect. Inputs: proposed CLAUDE.md invariants and the
security-critical paths list.

Co-Authored-By: Claude <noreply@anthropic.com>
…ce deps

The first dry run's green gate failed at both commits with
`Failed to load url @antseed/protocol/billing`: a fresh worktree had only
`pnpm install`, and workspace packages resolve each other through their
gitignored dist/. red-green.sh now runs `pnpm --filter '<pkg>^...' run build`
after install (what CI has before a package's tests). gate-red.sh also runs
the red in a fresh worktree, so this shows up before any fix is written.
red() and fix() resume idempotently: existing commits are gated first and
the session is skipped when the gate passes.

Co-Authored-By: Claude <noreply@anthropic.com>
The second dry run stopped at the read-only gate: the verify session's
`ln -sfn $(readlink -f $d) ...` over an unmatched glob created a symlink named
`*` in the fix worktree (its persisting cwd). Read-only stages (intake, verify,
blast radius, reviewers, judge) now start in runs/<slug>/scratch/<stage> with
the worktree added by path; native-step cds the pane into --cwd so the
transcript path matches; the preamble keeps scratch and evidence under the run
dir and asks for a clean `git status` before answering; verify cites the
gates' fresh red/green instead of rebuilding them by hand; stop() logs a
decision row. Proposals from the run's reflect (read-only bind mount, Stop
hook, quarantine) stay open.

Co-Authored-By: Claude <noreply@anthropic.com>
…eviewer

Run 3 stopped with `could not split pane wB:pN`: the judge pane was split from
reviewer A's pane, which native-step closes after a successful, audited
session. The judge now gets a pane from the run tab's root pane. Resuming the
review stage redoes a round with no judgment and reuses the reviews already
answered in it, instead of skipping to the next round.

Co-Authored-By: Claude <noreply@anthropic.com>
In run 3, reviewer C (gpt-6-astra) never saw the diff: `git -C <wt> diff`
does not match the `Bash(git diff:*)` allow prefix, so plan mode asked the
model's safety classifier, and gpt-6-astra answers that with HTTP 400. The
driver now writes review-input/ (diff.patch, log.txt, changed files at HEAD)
and the review and judge prompts point there for Read. A review whose session
got non-200 responses carries an audit note for the judge. Readers no longer
get the preamble's decision-log rule.

Co-Authored-By: Claude <noreply@anthropic.com>
`git -C <dir> push` matched no deny prefix; only the dead pushurl stopped it.
Session settings now come from lib/session-env.mjs with five independent
layers: deny rules for every push form, remote/url/hooks/credential config,
inline `git -c` and `--no-verify`; `url.<dead>.pushInsteadOf` for https, http,
ssh and git@ (catches inline `-c remote.origin.pushurl=<real>`); a dead
origin pushurl; core.hooksPath with a pre-push that refuses unless
FACTORY_ALLOW_PUBLISH=1; and no credentials (helpers reset, unusable
GH_TOKEN, prompts and ssh off). scripts/check-push-gap.sh proves seven routes
refused (dry-run pushes to a throwaway ref) plus the hook and publish gates;
a pinned session proved the deny rules refuse `git -C <wt> push`,
`cd <wt> && git push`, `git -c ...` and `git remote set-url`.

Co-Authored-By: Claude <noreply@anthropic.com>
…eer 1, item 2)

- Review reuse on resume needs the review's recorded HEAD to equal the
  current HEAD and the review to be undegraded (no non-200 responses, e.g.
  gpt-6-astra's refused classifier calls); a sidecar review-r<n>-<model>.json
  records head, tag, degraded and refused codes.
- Every terminal outcome writes a decisions row: driver errors (labelled
  FAILED (driver error)) and PASS/STOPPED, not only gate stops.
- auditStep reports `degraded` for any non-200 message response.
- gate-green fails when the fix commit touches a test file.
- verify.sh copies each sandbox report directory into verify/<label>/reports.
- Preamble states the Bash tool's 600 s cap; blast radius no longer offers a
  /tmp git worktree.
(The judge-pane anchor fix landed earlier in cfd131362.)

Co-Authored-By: Claude <noreply@anthropic.com>
…g (steer 1, item 3, D3)

scripts/run-bounded.sh runs a command with a deadline and, on timeout, kills
every process it started (they carry a unique FACTORY_RUN_ID in their
environment, which survives setsid and reparenting; TERM, then KILL), and also
reaps background leftovers of a command that exits on its own. verify.sh uses
it instead of `timeout`; scripts/run-scenario.sh is now the only way a verify
session may run an extra scenario (deadline, `pnpm sandbox down`, recorded).
native-step counts the session's background-task output as activity and, when
a tool call has been silent for --tool-ceiling-min (20), presses Escape and
tells the session to finish without it (a second hang fails the step).
Proofs: check-run-bounded.sh (hung tree with a setsid'd grandchild and
xvfb-run: exit 124, nothing left) and a pinned smoke session whose silent
7-minute command was interrupted after 1 minute and which then answered.

Co-Authored-By: Claude <noreply@anthropic.com>
D1: run-with-evidence does not exit after a failed desktop launch; reproduced
through run-scenario.sh (scenario FAILED at waitForLoadState, tree killed by
the 240 s deadline, `Failed to shutdown` in desktop-main.log). D2: the desktop
window never loads under Xvfb on this VPS; `--disable-gpu` was tested and
removes the GPU errors but does not fix the load, so the cause is unverified.

Co-Authored-By: Claude <noreply@anthropic.com>
…(steer 1, item 5)

tooling/claude-factory is rebased onto origin/codex/sandbox-stack (a441f1d),
so .claude/skills/verify-antseed and sandbox-test are files next to the
factory. Prompts point at them, verify-plan --audit reads the local table, and
preflight requires the sandbox stack to be an ancestor of the factory branch.

Co-Authored-By: Claude <noreply@anthropic.com>
… 1, item 6, D4)

The sandbox-test / verify-antseed / CLAUDE.md / e2e README part of item 6
landed on codex/sandbox-stack as d21c468; after the rebase only this
factory README line remains here.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude Code asks before any write under the project's .claude/ even in
acceptEdits mode; a factory session can wait on that prompt forever, and the
stall nudge (Enter) would pick its default "Yes". In 2.1.302 no allow rule
satisfies the check (tested: Edit/Write(//<root>/.claude/**) and the grant's
own Edit(/.claude/**), in the settings file and in --allowedTools). So:
- prompts are detected from the transcript (a Write/Edit with no tool_result,
  or Herdr `blocked`), not the screen, which a narrow pane garbles;
- a file under the session's own write root's .claude/ gets Claude Code's
  session grant (option 2), selected by native-step; the driver passes the
  write root only for stages that write product files (not readers, read-only
  stages or reflect);
- every other prompt is refused with Escape and fails the step at once
  (exit 5, a clear driver outcome);
- the stall nudge never presses Enter while a tool call is pending.
Proof (pinned opus-5.5 smoke sessions): A wrote <root>/.claude/skills/smoke/
SKILL.md with no human (grant at +5 s, done in 27 s); B, a .claude/ write
outside its scope, was refused and failed 5 s after the prompt, nothing written.

Co-Authored-By: Claude <noreply@anthropic.com>
…vers

It scanned every Xvfb and sleep on the machine, so any other Xvfb (another
session's desktop run) failed it. run-bounded.sh now prints its run id; the
check reads it and lists only processes whose /proc/<pid>/environ carries that
FACTORY_RUN_ID. Proven with a decoy `xvfb-run -a sleep 120` running alongside:
RUN_BOUNDED OK (exit 124 after 7 s, 7 marked processes, none left).

Co-Authored-By: Claude <noreply@anthropic.com>
The dry-run body was 199 lines. Its red section kept only the last 60 lines of
output and so omitted the headline -1500000n -> "-1.-5" case. Its
security-critical reason quoted the glob's general description, and it leaked
machine paths (/root, /tmp, ~/.antseed-sandbox) and factory-internal file
references.

scripts/pr-body.mjs now writes, in order:
- the security marker naming only the matched glob and files
- the issue link, or "no GitHub issue: task-file run"
- the fix in the fix commit's own words
- test level and file
- the key red assertion with the red signature, the red summary and the
  green summary
- verify rows pass/fail from each row's own commands, with sandbox scenarios
  and pre-existing failures named
- one line per reviewer and the judge verdict
- everything else in one collapsed <details> block, with paths cut out

publish.sh refuses the body if it contains a path or factory-internal
reference, lacks the red signature, has a main part over 60 lines, or has
more than one <details>. The old body fails all three content checks: 8 leak
lines, 0 signatures, 199 main lines.

Co-Authored-By: Claude <noreply@anthropic.com>
… shows both doctor runs

Under `## Fix`, the body now adds one sentence naming the files deliberately
left unchanged and why. It is generated from the run's scope note
(runs/<slug>/scope.json: decided_by, decision, left_unchanged[] with path,
what and source, and reason), and the body fails if a listed path does not
exist at HEAD. The evidence table now includes the baseline rows, labelled
"sandbox, without this change" and "sandbox, with this change", so both
doctor runs (before and after the fix) and both teardowns show. The
security-glob narrowing stays on the backlog.

Co-Authored-By: Claude <noreply@anthropic.com>
…branch names

intake.sh --note-file (factory.mjs --note-file) appends the human's notes to
task.md under "Operator notes (from the human, not part of the issue)": hints
to verify, and decisions reserved for the human. A slug that already starts
with the issue number gives fix/<slug> instead of fix/<n>-<n>-....

Co-Authored-By: Claude <noreply@anthropic.com>
--workspace <label> (default bug-<issue>, or <slug> for a task file) makes the
driver create that workspace (lib/workspace.mjs), record its id in the run
state, and put the run tab, every stage pane, the reviewers and the judge in
it instead of HERDR_WORKSPACE_ID. On resume it reuses its own recorded
workspace; a label held by any other workspace is refused, never reused.
Runs whose state predates this keep their recorded tab's workspace. A slug
equal to the issue number gives branch fix/<n>.
Proof, scripts/check-workspace.mjs: fresh workspace wC, pinned session in
wC:p2 answered (audit ok), the 6 other workspaces were unchanged, and wC was
closed: WORKSPACE OK.

Co-Authored-By: Claude <noreply@anthropic.com>
Replace the scripted state machine (factory.mjs, stage prompts, gate
scripts) with one Claude Code session running pstack 0.15.25's
poteto-mode as an autonomous run. The factory now only says where
things are, maps pstack's Cursor names to Claude Code, and adds what
pstack cannot know about AntSeed.

- RUN.md: the first message. Task, exit condition, AntSeed files from
  the tooling checkout, and the Claude Code mapping (Agent tool, todo
  file, gate wake watcher, deslop fallback, orch ledger and gates).
- pstack-models.md: pstack's per-role model table. Opus writes code,
  astra verifies, panels mix opus, fable, astra and sol.
- agents/astra.md, agents/sol.md: general agents pinned to gpt-6-astra
  and gpt-6.1-sol.
- start.sh: worktree, task, orch store, session settings (GesundAI
  pin, no push, Claude Code Bash sandbox, deny rules), model preflight,
  resumable start.
- check-push-gap.sh: pushes to a local fixture with positive controls.
- verify-antseed: features/ map in pstack's create-verification-skill
  shape, proof on the matching surface, user-visible review evidence,
  security-critical paths, and prove.sh for mechanical red and green.
- Area CLAUDE.md guides for buyer-core, contracts, node, protocol,
  cli, gateway and desktop.

Tooling branch only, never pushed.
… FACTORY_RESUME reopens a session

Claude Code makes a --plugin-dir read-only to the session's Bash sandbox, and the plugin
was .factory itself, so runs/<slug> (orch ledger, gates) could not be written.
…ude Code mapping

- Verification order follows pstack: self-verify, interrogate, deslop and no-comments, then one independent verifier at the code-ready head (Autopilot-full step 4, Orchestrate's Verification).
- Cursor's /loop is Claude Code's /loop for Autonomous run step 2. Reasoning level maps to the Agent effort parameter, with a budget line.
- Read-only reviewer agents (reviewer, astra-reviewer, sol-reviewer) stand in for readonly: true.
- Reflect rows aimed at pstack files are upstream proposals, never local edits.
- start.sh prepares the fix and scratch worktrees (forge-std, install, build with the .nvmrc Node), retries network steps, matches model aliases, and gives prove.sh and agent-browser writable dirs in the run.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant