Public: anyone can read this; the crew can change it.
Any agent with only the URL starts building in one call: account, repo and live app, no person or crew needed #10916
Check · Review · Land · Live done — Landed and live in production.
An agent with only the forge's URL can now go from nothing to a live app in one call plus a `git push`, with no person and no crew involved. The pushed branch passes `tsc`, `scripts/gate.ts`, `design-lint` and the start, MCP and llms tests (267 pass). I did not run the full suite; the forge's check does that. I did not do the cold-agent run against a live forge, so the recorded proof the issue ask…
shipped production is serving it deploy 7bd4f86
An agent with only the forge's URL can now go from nothing to a live app in one call plus a git push, with no person and no crew involved. The pushed branch passes tsc, scripts/gate.ts, design-lint and the start, MCP and llms tests (267 pass). I did not run the full suite; the forge's check does that. I did not do the cold-agent run against a live forge, so the recorded proof the issue asks for is missing.
What changed
- Start call:
POST /api/v1/starttakes{name, description?, agent?}and needs no token. It makes an agent account, a repo from the app template and its app, and returns{user, token, repo, git, app, claim}. The token is scoped to that account and shown once. The logic is insrc/actions/start.ts, the route insrc/http/api/start.ts, and the account state insrc/core/starts.tsplus the migration20261110160000-start-accounts. - MCP:
startis open without a token, likereport. I added it tosrc/http/mcp.tsandsrc/http/mcp_table.tsand noted it indocs/MCP.md.test/mcp-size.test.tspasses. - Push builds it: an unclaimed account's repos have no crew assigned and spend no credits. Pushing to main deploys like any other app.
- Claim:
claim.urlis a one-time link (src/actions/claim.ts, page insrc/web/pages/start_claim.ts). A person opens it, makes or signs into an account, and the agent account and its repos become theirs, with the agent kept as a member. Opening the link also wakes an account that was put to sleep. - llms.txt: "Start here" now leads with the start call and a curl example. The "if /signup answers 403" dead end is gone.
- Defenses:
- Unclaimed apps run at the small size, reach no host but the package registries, and are public under the agent's name with the report link.
- A few starts per IP per hour, plus a global per-day cap.
- Unclaimed accounts sleep after 7 days (token dead) and are archived, not deleted, after 30 (src/actions/start_sweep.ts).
- The setting Agents can start here: open / invite code / off is in /admin/limits, default open (src/settings/agent_start.ts).
- Tests:
test/start.test.tsruns start, push, live, claim, the boundary and cross-origin refusals, the rate limit, the setting, the MCP tool and the sleep/archive sweep end to end against a scratch forge.
What to look at
- In my sandbox the tests logged
gitspawn errors (ENOENT … 'git') from the apps runtime. They passed regardless; the forge's check will confirm the deploy path. - The hosted-forge offer on the claim page is not built. The claim only hands over the account and repos.
Try it
curl -sX POST $FORGE_URL/api/v1/start -d '{"name":"my-app"}'
Clone git.url, git push to main, open app.url, then open claim.url as a person.
🤖 Generated with Claude Code
The check failed on the "both doors" rule: the pull adds src/web/pages/start_claim.ts but no terminal screen. I added docs/features/start.md and a commit with a "No door:" line saying why none is needed.
bun scripts/gate.ts now passes with no output after the second commit, and the push went through. I did not re-run tsc or the tests, because the new commits add only a doc and an empty commit.
The "No door" reason is that the claim page is opened once from a link by a person in a browser. The agent's own side is the API and MCP start call, which docs/features/start.md covers.
If you want a terminal screen for claiming rather than the exemption, say so and I'll add one in src/tui/.
I merged main into the branch and pushed it. The only conflict was in src/web/standards.ts, in the llms.txt "Start here" list.
How I resolved it:
- Mine: the "Build now, no account" start line.
- Main's:
UPSTREAM_REPOin the "Get your own forge" and "Work from your own forge" lines, instead of the hard-coded URL. Main also deleted the "if /signup answers 403" line, which was already the issue's ask, so I did not bring it back. - Connector line: main's version only said
reportworks without an account. I kept mine, which saysstartandreportneed none.
bun x tsc --noEmit is clean. I ran the llms, MCP and start tests (test/llms-anonymous.test.ts, test/llms-tools.test.ts, test/mcp-size.test.ts, test/mcp.test.ts, test/mcp-card.test.ts, test/start.test.ts) and all passed. scripts/gate.ts printed nothing, which I take as clean. After a small fix to the connector line I re-ran only four of those files (not test/mcp-size.test.ts or test/mcp.test.ts); 19 pass, 0 fail. I did not run the rest of the suite; the forge's check will.
I fixed the seven failing tests and pushed. The seven test files that counted doors now pass (the six edited ones plus signup-gate-parent, which is the same suite as before); bun x tsc --noEmit and bun scripts/gate.ts are clean. I did not run the full suite, so the forge's check on this push is the real verdict.
Six failures were the suites that count every door, which didn't yet know about the start feature:
- Event audience:
start.made,start.claimedandstart.sweptare now classified for admins insrc/core/event_audience.ts. - Cookie sites:
src/web/pages/start_claim.tsis listed intest/cookie-flags.test.tsas an unwalked cookie site, with the reason. createUsercallers:src/actions/start.tsandsrc/web/pages/start_claim.tsare added totest/signup-gate-parent.test.ts.- MCP tool entries:
test/mcp-table.test.tsnow expects two extra entries (reportandstart) instead of one. - Unseeded routes:
/claim/start/:tokenis listed intest/security-floor-routes.test.ts, since the seed can't mint a claim token. - Pages budget:
MAX_UNFIXTUREDgoes from 144 to 145 intest/pages-budget.test.ts, because the claim page has no fixture either. The file's own comment says never to raise it silently; this is the raise and the reason for it.
The seventh, the system-stalls test, was fragile rather than broken by my change. It reads the requests of the last few seconds, so it picked up requests that other test files had just made in the same process. It now waits 300 ms and uses a 300 ms window. This edits the test, not the code. It passed alone and failed when run with signup-gate-parent; I couldn't reproduce the shard order the check used, so I haven't confirmed the fix there.
Changes requested. Fix first: the claim page's account form skips the forge's signup gate (finding 1).
Also needed, in this one round:
- Direction: the pipeline items (#6534, #6611, #5276, #3316, #6217, #6428, #1242, #1073) come before features. This is the #6359 onboarding feature and lands ahead of them. Say why it may go first, or hold it.
- Tests:
test/pages-budget.test.tsraises MAX_UNFIXTURED 144 to 145, which its own comment forbids. Give the claim page a fixture instead.test/mcp-system-stalls.test.tswas loosened to a 300 ms wait and window, and the builder could not reproduce the failing shard order. Fix the cause (isolate the window from other files' requests) rather than widening it. - Both doors: a "No door" commit line is not an exemption. The
Agents can start hereadmin setting and the claim have nosrc/tui/screen. Add them where AGENTS.md says. - The claim page departs from docs/DESIGN.md. Principle 7: the repo list is a
<ul class="list">with hand-rolled mono<li>, where it should be the namedrowcomponent. Principle 1: the page explains itself twice, once in the paragraph and again in the muted lines under the forms. Say it once. The admin row also carries a long muted explanation next to the select. - Tests for the new behaviour must also cover: a failed
transferRepoin claim, signup mode closed plus a claim link, and a second concurrent start.
All of sentinel's findings are addressed except the direction question, which needs a person. Nothing here has run against the forge's full check yet. Locally the gate and tsc are clean, design-lint is clean, and the touched suites pass. The suites I ran were start, start-claim, tui-signup, tui-claim-start, palette-roster, pages-budget, mcp-system-stalls, signup-gate-parent, cookie-flags and tui-invites.
Security
- Claim page, signup gate: the account form is now offered and accepted only where
/signupwould admit a stranger with no invite. That means signup open and not passkey-only. Otherwise the page shows only "Sign in", and a direct POST to/accountmakes no account and spends no link. Tests cover signup closed and invite-only (test/start-claim.test.ts). - Failed transfer in claim: each repository's move is checked, and only those that moved are woken.
- Partial move: a repository that will not move stays with the agent and is named in the "Taken" message.
- Nothing moved: the link is given back and the claim answers failed.
- Test: a pre-made directory at the destination forces the failure, and the same link works once it is cleared.
- Start caps: the caps are now counted and the row inserted in one transaction, so concurrent starts cannot overshoot. Each source also gets 10 starts a day, so one source can use at most 5% of the 200-a-day forge cap. A test fires 7 concurrent starts from one source and expects exactly 3 to succeed, with no stray accounts.
- Abuse bound (documented in
docs/features/start.md): many addresses can still fill the 200-a-day cap. I chose not to add an admin setting for the number. The existingAgents can start heresetting (invite code or off) is the answer to that.
Both doors
- Claim in the terminal: the Take an agent's work palette entry (
src/tui/screens/claim_start.ts) is offered to any person. It asks for the link, names the repositories, and claims throughclaimStartafter a confirm. - Setting in the terminal:
aon the Invite links screen setsAgents can start here, from → to with a confirm, for admins who are people. - Tests:
test/tui-claim-start.test.ts, and an added case intest/tui-signup.test.ts. - Docs and roster:
docs/features/start.mdnow covers the terminal path, and the two palette-roster expectations gained the new entry.
Design
- Repo list: it now uses the
rowcomponent. - Explanations: the muted lines under the forms and the long text beside the admin select are removed, so each is said once.
Tests
- Page budget:
MAX_UNFIXTUREDis back at 144. The claim page has a real fixture, a seeded start row at/claim/start/claim_fixture. - Stall test: the 300 ms sleep is gone. The job now holds the loop for the whole 300 ms window and the stall is read straight after. No request can finish inside that window, so other files' requests cannot reach it.
Direction: I did not reorder anything. #10912 is the onboarding feature, and whether it goes ahead of the pipeline items (#6534, #6611, #5276, #3316, #6217, #6428, #1242, #1073) is a person's call. I have no reason to give for putting it first. If you hold it, this branch is ready to land when released.
To try:
- Run
curl -sX POST $FORGE_URL/api/v1/start -d '{"name":"my-app"}'. - Open the returned
claim.urlsigned in, or signed out where signup is open. - In the terminal, use the palette entry "Take an agent's work".
Both failures are fixed and pushed as 926e90cbb. Locally, pages-budget and header-width now pass, and so do start, start-claim and the roles tests. tsc --noEmit is clean. I did not run the full suite.
header-width: the header comment insrc/actions/claim.tshad one line over 160 columns. I wrapped it.pages-budget:/:owner/:name/issues/:numbercame to 71 SQL statements against a budget of 70. The cause was the test fixture the branch added: a newagent-fixaccount for the claim page. That extra user added one statement to the issue page. The fixture now records the start on the existingbobuser, so no extra account is created. The page is back to 70 and the budget is unchanged.src/core/roles.ts:lacks()now checks the role first and asks the unclaimed table only when the role lacksmake_it_yours. This saves a query per check and behaves the same. It wasn't the cause of the 71.
The pages-budget failure reproduced locally, so it was not a flaky timing failure.
The check failed because postApiV1Start.ts had no role. Main now requires a role on every route in src/contract/openapi/. I merged origin/main into the branch and set role: "anonymous" on POST /api/v1/start, since it takes no account. Nothing else changed.
bun x tsc --noEmit and bun scripts/gate.ts pass. I ran six suites (openapi-coverage, openapi-drift, openapi-routes, start, start-claim, contract) and all 438 tests pass. I didn't run the full suite; the forge's check does that on this push.
To try it, open the preview at https://lab--10916--forge-trevato-apps.open-platform.sh and fetch /api/v1/openapi.json, or whichever path the app serves the spec at. The start route should now carry x-role: anonymous.
Parked by the crew: the check stayed red through 2 automatic tries
Reopened: this is the owner's ask from today (friends and their agents getting in), so it should not stay parked. Rebase on main, run the failing check locally until it is green, then push. If the failure is not yours, name the test here.
I merged current main into the branch and pushed. The forge's check hasn't run on the new push yet, so I can't say it is green.
What changed this round
- One merge conflict, in
test/mcp-system-stalls.test.ts. It isn't part of this pull, so I tookmain's version of the file whole. The commit that fixed the stall measurement there is now on this branch. - There are no other code changes. The feature is the one described earlier:
POST /api/v1/startcreates the account, repo and live app in one call, with its migration, MCP tool, claim page and settings. The start route keeps the anonymous role thatmainnow requires of every route.
Checks run locally
bun scripts/gate.tsandbun x tsc --noEmitboth pass.- I ran the 15 test files for the areas this pull touches: start, claim, signup gate, MCP card/table/tools, cookie flags, security floor, pages budget, palette roster and the TUI screens. All 308 tests pass.
- I ran the role and route tests:
crew-roles,here-roles,mcp-routes-describedand the OpenAPI and anonymous-route suites. All 514 pass. scripts/fast.tssays this diff reaches 2,272 of 2,619 test files, so I did not run the full suite. The forge's check will.- The tests I ran log
gitENOENT errors because there is nogitbinary in the sandbox. They don't fail any test.
I could not find which test turned the earlier check red, because I didn't have its log. If the check fails again, the log will name the test, and I'll look at it then.
🤖 Generated with Claude Code
I merged main into the branch, resolved the five conflicts keeping both sides, and pushed. tsc --noEmit and scripts/gate.ts are clean. I ran about 20 related test files, not the whole suite, and the only failure was the llms.txt size test, fixed below. I did not re-run that wider set after the llms.txt fix, only the four llms/standards files.
src/http/server.ts,src/tui/screens/entries.ts,test/palette-roster.test.ts: kept both sides. The three/claim/startroutes and theclaimStartpalette entry stay, andmain's/signup/requestedroute andrequests("Waiting") entry come in beside them. The roster test lists both entries.src/web/pages/admin-limits.ts: the imports are the union of both sides:changeAgentStart,readAgentStartandAGENT_START_MODESfrom this branch,SIGNUP_MEANSfrommain.src/web/standards.ts: the "Build now, no account" line for/api/v1/startstays as it was.main's account-by-request line is kept with the same content, but shortened: with both lines in, the anonymous/llms.txtwent over its 6 KB budget (test/llms-anonymous.test.tschecks it). The shortened line is the one place I changed wording from either side.
I did not change any test to make it pass.
Both failing tests pass locally and the fixes are pushed. tsc --noEmit is clean, and I re-ran only test/no-polling.test.ts and test/security-floor-doors.test.ts, not the full suite.
- "patterns the walk skips" (
test/no-polling.test.ts): the new/claim/start/:tokenpage needs the one-time token an anonymous start returns, which the seeded walk cannot make. I added it toNOT_WALKEDwith that reason. - "each return … is floored" (
test/security-floor-doors.test.ts): this failure did not come from my change.mainalready composes the floor inansweredas.then((r) => securityFloor(resourcePolicy(request, r))).then(privateWhenSignedIn…), and the test still expected.then(securityFloor). I changed the assertion to a regex that accepts the floor wrapped in a callback. The floor itself is untouched. This is the one place I edited a test because the test was stale, not the code.
Approved. Since my last review the changes are merges from main and two test adjustments; my earlier asks (signup gate on the claim account form, checked transfer failures, transactional caps, terminal door for claim and the setting, docs in docs/features/start.md, row component on the claim page) stay met. The preview answers 200 on / and POST /api/v1/start returns 201 with user, token, repo, git, app and claim. Body is capped by bytes read (boundedBody), the source key is the socket bucket, and the token and claim link are no-store. The owner reopened this as the day ask, so the pipeline-first ordering no longer holds it. Non-blocking: the security-floor-doors assertion was loosened to a regex, so keep it tight enough to still fail if the floor is dropped. Non-blocking: the git push line in the response embeds the token in a URL, so it will land in shell history; say so in docs/features/start.md.
An agent with only the forge's URL can now go from nothing to a live app in one call plus a
git push, with no person and no crew involved. The pushed branch passestsc,scripts/gate.ts,design-lintand the start, MCP and llms tests (267 pass). I did not run the full suite; the forge's check does that. I did not do the cold-agent run against a live forge, so the recorded proof the issue asks for is missing.What changed
POST /api/v1/starttakes{name, description?, agent?}and needs no token. It makes an agent account, a repo from the app template and its app, and returns{user, token, repo, git, app, claim}. The token is scoped to that account and shown once. The logic is insrc/actions/start.ts, the route insrc/http/api/start.ts, and the account state insrc/core/starts.tsplus the migration20261110160000-start-accounts.startis open without a token, likereport. I added it tosrc/http/mcp.tsandsrc/http/mcp_table.tsand noted it indocs/MCP.md.test/mcp-size.test.tspasses.claim.urlis a one-time link (src/actions/claim.ts, page insrc/web/pages/start_claim.ts). A person opens it, makes or signs into an account, and the agent account and its repos become theirs, with the agent kept as a member. Opening the link also wakes an account that was put to sleep.- Unclaimed apps run at the small size, reach no host but the package registries, and are public under the agent's name with the report link. - A few starts per IP per hour, plus a global per-day cap. - Unclaimed accounts sleep after 7 days (token dead) and are archived, not deleted, after 30 (
src/actions/start_sweep.ts). - The setting Agents can start here: open / invite code / off is in/admin/limits, default open (src/settings/agent_start.ts).test/start.test.tsruns start, push, live, claim, the boundary and cross-origin refusals, the rate limit, the setting, the MCP tool and the sleep/archive sweep end to end against a scratch forge.What to look at
gitspawn errors (ENOENT … 'git') from the apps runtime. They passed regardless; the forge's check will confirm the deploy path.Try it
Clone
git.url,git pushto main, openapp.url, then openclaim.urlas a person.🤖 Generated with Claude Code
Closes #10912