Version 0.4.28 · Published to npm. Scaffold with bun create broapp my-app. View on npm

Broapp
GitHub

Autoapp: the phase backlog

What the nine build prompts deliberately left out, why, and what would have to be true before each item is worth doing. The design is in design.md, the approval model in security.md, the platform evidence in packaging.md.

This file proposes. It does not reopen the decisions phase fixed: the branch, the package boundary, trusted local code, no generated browser code, the effect vocabulary, the three-row policy, approval and channel identity, forward-only migrations, or the run outcomes.

Deferred on purpose

ItemWhy deferredPrecondition to start
A scheduled channel. Work an application does on a clock — "refresh every five minutes" — as a fourth channel in the gate policy, confirm for write and external as workflow is, with a cadence the person sets and sees in the panel, and every scheduled run recorded like any other.Host code runs when a route is called and at no other time; a call the host makes on its own timer passes no gate, is recorded in no run and cannot be checked. The news application's intent (--) asked for a five-minute refresh and shipped a globalThis.cron behind a typeof guard that did nothing. a tells the engineer such work is outOfReach and never a timer inside start.A design for who owns the clock (the launcher, not the child), what the person is asked when a cadence is set, what a scheduled external call asks at run time, and how a missed run is shown. Until then, no timer inside start.
A criterion the plan itself makes external. intent.task refuses a criterion naming an external route of the serving release; a route a task declares for the first time is not in that list.knownExternal is the contract as it stands, and a plan's routes are words, not a contract; guessing a route's effect from a summary would refuse good criteria. The words ("after activating", "not in a preview") catch most cases, and the build refuses a step on the route once it exists, so the task fails honestly on "No example named … was run".A plan field that declares each route a task adds with its effect, checked against the contract when the task completes, so the planner's own external routes join the list.
Enforced capabilities. An OS sandbox for application children: sandbox-exec profiles on macOS, Landlock or bubblewrap on Linux, a restricted token or AppContainer on Windows. Or a broker model where privileged work is available only through launcher-provided operations.v children are trusted local code; the manifest describes, the gate decides, nothing contains. Doing containment per platform before the loop existed would have cost the loop.A non-engineer audience, or a single report of a generated change reaching outside its data directory. Start with the broker model: it is portable and the gate already has the external effect to hang it on.
Custom browser code per application. Generated React or JavaScript views, which need an origin of their own and therefore a Brobridge change (an explicit same-site embedding allowlist) or a second document route.The renderer covers tables, forms, actions and status. Generated browser code breaks the "one pinned document" property that makes the CSP a guarantee.A view the renderer cannot express that a real user needed, recorded in a report. Then design the Brobridge opt-in first and review it there.
Renderer primitives. A pure navigation action; charts; file upload and download inside the data directory; a list-detail layout; a stream-backed live table using useStream.Prompt 's set was the minimum for Notes.Each is small; add when a demo needs it. Navigation first — see the finding below, where a button that changes nothing has to call a read operation to get anywhere.
Workflow failure policies beyond stop: continue, retry with a count, compensation steps.Compensation is where "undo" becomes a lie without care; stop is honest.A workflow with external steps in real use. Compensation for external effects stays manual and explicit.
Workflow capture from demonstration (record what the person clicks, not what the model called).Research-grade problem; the tool-call sequence from a run is structured already and covers the demo.Evidence that people want to save what they did by hand, not what they asked for.
external operations over MCP.External effects driven by an external agent through a tab approval is two hops of trust; keep it out until the approval UI shows enough context.The approvals strip showing the operation's declared hosts and paths from the manifest.
Approval by MCP elicitation instead of the tab.Elicitation support varies by client; the tab is one place that always works.Two mainstream clients supporting elicitation. Keep the tab path; add elicitation as a second approver.
A renderer kind with rich interaction (the painting requirement): a canvas, an editor, a map — a component whose behaviour is more than a table, a form or a button can declare.The renderer draws what a view specification declares and nothing else. Rich interaction is either browser code an application ships, which the design forbids, or a large new kind in the pinned renderer, and neither should be designed from one sentence. f gave applications a theme contract and stopped there.The reports reproduced and filed under prompts/autoapp/reports/: what the person asked for, what they needed to do, and why the six kinds could not do it.
Verify a third-party component in the compiled page. Before the framework adopts a component library for a renderer kind, prove the component renders inside the one pinned script and stylesheet broapp build produces, and loads nothing off-origin.The harness landed in g and the first third-party component is verified through it: the panel's vendored shadcn select, on Radix, opened so its portal is measured, under three themes and three schemes. It found a real fault — portalled content sat outside the panel's token scope and read no colours at all. Still no component library in the renderer, so there is still nothing there to verify.Adding the component as a target in scripts/theme-check.ts is now part of "Adding a component" in the common rules, so the next one is verified by the gate rather than by a decision. What is not covered: only one portalled component, and only its colours, corner and family — not its behaviour, its focus order, or its measurements under a long word. One finding is still open: the panel's border utility draws in currentColor, because Tailwind's preflight is off and nothing replaces the part of it that sets a default border colour, so a popover's hairline is the text colour. Fixing it changes every border in the panel at once, which is its own decision.
Browsing without asking. A standing approval for web.search and web.read, as a's switch is for edits and builds.Landed: the person said the cards were the cost, so a's switch now covers the two web tools through STANDING_WEB in engineer/standing.ts, and the launcher's log names each answer by the tool alone. Still open: a backlog run's stand-in never covers them, and the card remains the one place a person sees a query or an address before it leaves the machine while the switch is off.Done for the person's switch. A per-tool switch — edits without asking but searches still asked — is its own row if anyone asks for it.
A page the engineer can click. web.read returns text and links; a form, a search box on a site or a page behind a click are out of reach.Bun.WebView can click and type, but every action is another external call and another card, and the address guard has nothing to check on a click.A request that a search plus a read could not answer, in a report.
Another search engine, or a key-backed one. DuckDuckGo's script-free results page is the one engine, with no key.It works with nothing configured; a keyed engine is a secret to keep and a setting to explain.The results page changing shape, or a person asking for a different engine.
Prune. Deleting data-prev-*, snapshots, previews and old releases.Recovery must never lose data; deleting is a separate, explicit action.Design a prune command that lists what it would remove, requires --yes, and never removes the current release, the last two data-prev, or any snapshot younger than seven days — and trash/, oldest first, never younger than seven days. Now also wants a retention rule for runs.sqlite — see the findings.
Rendered checks. An acceptance step that opens the preview in a browser and asserts what is visible, so a change to a page can be verified without a person looking.Half of this landed in g. bun run theme-check is a headless browser over the built page: Playwright Chromium as a root devDependency only, never a launcher dependency, installed separately (bun x playwright install chromium) and skipped where it is missing. It covers the theme — the renderer's button and the panel's select, nine theme/scheme combinations, computed colours, corner, family and contrast. What it is not is an acceptance step: nothing a candidate declares runs a browser, and a person approving a change still sees no rendered evidence.A rendered-behaviour failure that a view step could not have caught, recorded in a report. The harness is now there to carry it: an acceptance step would drive a preview child through the same browser, which needs a bridge, a data copy and a decision about what a step may assert.
Fail-before, pass-after. Run a new or changed acceptance example against the current release as well as the candidate, and say whether it distinguishes them.The instructions ask for an example that fails today; nothing checks that it does. A current-release preview is a second child and a second data copy per check.Two reports of an example that passed before the change it was written for.
Structured view edits. Operations that set a property, remove a component or move a child in views.ts, producing a reviewable diff, instead of hunks over TypeScript.views.ts is TypeScript, not JSON: expressions, imports and helpers make a structural edit more than a patch to a tree, and the rules for what is unsupported have to be written first. Hunks land at three for three since c.Hunk edits to views.ts failing to match in real use, counted in the knowledge store.
A page's heading apart from its name. title is both the heading the renderer draws and the page's name in navigation.One field for both is one less thing to get wrong; nobody has asked for them to differ twice.A second request to change one without the other. Then an optional heading that the renderer prefers and navigation ignores.
Hash-guarded whole-file replacement. source.change for files over sixty lines when the caller names the hash it read, with a reviewable diff and the existing git undo.The sixty-line cap is what b measured: a B model spent minutes composing one rewrite; hunks have landed three for three since c.A refused rewrite that hunks could not express, counted in the knowledge store.
Batch reads with line ranges. Several paths and ranges in one source.read, per-file hashes, an "unchanged" answer.Reads already carry the workspace revision, and a read costs a call, not many tokens; nothing measured says reads are where a turn's time goes.The four-condition evaluation showing reads as the largest share of tool calls or tokens.
Template provenance in apps.list. Which starter an application came from and whether it still matches it.The orientation says what is built, verified and changed; "differs from the starter" has no decision hanging on it.A request from the engineer or a person that the orientation could not answer.
The theme CI job failed twice with "Could not resolve ./theme.css", the second time on a fresh checkout within a second of the test starting, before any browser work. The harness created that file before each build and removed it at the end.The bundler keeps a directory's listing for the life of the process; a file that appears after the listing is not found. Fixed after the second occurrence (scripts/theme-check/theme.css..): scripts/theme-check/theme.css is committed with a comment, the harness overwrites its content per theme and puts the comment back, and the test runs the harness in a beforeAll so a failure is a failed test with a stack.Nothing, unless it happens a third time; then the cause is not the listing.
Multiple launcher instances. Two launchers on one machine over one root.The control file and journal assume one.A lock file in <root> with pid and liveness check; serve refuses to start twice. Small; do it early in phase open. Partly done in m: open (and the bare command) against a root whose launcher answers on its control connection now asks it for a panel address and exits, instead of starting a second launcher; serve <appId> and the one-shot commands still do not check.
An opt-in idle exit for the launcher. Stop when no panel has been open and no application used for a set time.c made the launcher findable and stoppable (stop, status, Quit, and a line when the panel closes) and deliberately gave it no idle exit: an application is meant to stay up with the panel closed, and a launcher that stopped on its own would take it down.A person who wanted the launcher gone and did not stop it, twice. Then a setting, off by default.
Children of a launcher that is already gone. List and stop application children whose launcher died before c, when a child did not watch its launcher.Finding them means scanning the process table for a binary run with --child, per platform; c stops a child whose launcher dies from now on, and stop and status say nothing about older ones.An orphan reported on a machine that has run a c launcher since, which would mean the watch missed one.
The engineer running inside the application's tab (the original sketch).Avoided an IPC RPC surface for the launcher; the launcher tab is the engineer's home.If users keep two tabs open anyway, add launcher.* routes proxied through the app child so the app's AiChat can reach the engineer. Costs one more IPC message type and a second gate hop; the gate design already carries caller, so the audit trail survives.
Down migrations and data-restore UX.Forward-only by decision; the rollback boundary is documented.A UI in the launcher that shows, after a failed activation past the switch, the three honest options (compatible downgrade, forward repair, approved restore discarding later writes) and journals which was taken.
Rate limits and resource limits on children. CPU, memory, disk quotas.The child is trusted local code in v.Comes with enforced capabilities.
A second example beyond Notes, chosen from real use: document intake and reconciliation was one reviewer's guess.Unverified market pick.Evidence from anyone using the Notes loop for something else.
An .app bundle and signing. The other half of "download and double-click": a macOS bundle, a Windows installer, notarisation and code signing, so a person is not told to allow an unsigned binary before they see anything.The launcher is a bare executable per target, which is enough to run it and not enough to hand to somebody who has never used a terminal. Creation removed the other reason a downloader was stuck; this is what is left.An audience that downloads rather than clones. Signing needs certificates and an owner for them, which is an organisational decision rather than a technical one.
An AI panel in a created application's own tab, opt-in.The starter deliberately has none: broapp-ai-elements and the provider packages roughly triple what creation has to fetch, for something nobody asked for. The engineer in the launcher is the AI.A person asking the engineer for one, twice. Then it is a template variant or a change the engineer makes, not a change to the starter.
A run timeout, or a no-progress limit, on an engineer's turn. End a turn that has made no tool call for a set time, and say so, instead of letting a model plan in silence until the person gives up.In c a turn spent twenty minutes emitting nothing after its last edit. A limit set before knowing whether guidance fixes that would cut off turns that were about to succeed. Partly done after d, for the loops a tool can see: three cycles in a turn ending with the same failure stop and hand back to the person, a third identical read says so, and a cycle's progress survives an interrupted turn. Silence between tool calls is still not bounded; the replay and evaluation harness give a turn twenty minutes. A backlog run's builder turns have one since d: eight minutes with no tool call, the clock held while a tool runs or a question waits (intents.md); the first measurement of a no-progress limit is that run's, and an ordinary engineer turn still has none.d's measurement shows the stall persists with guidance.
Documents and hints on and off from the tab. A person switching a turn's orientation, evidence or build-failure hints off from the Knowledge panel, as the replay and evaluation already can.k gave the tab a window on what is served and the four lesson changes, and deliberately nothing that changes what a turn is given: the evaluation found orientation alone made completion worse, and a switch in the tab would turn that finding into a setting nobody measured.An evaluation on a second model saying which documents help it.
Automatic promotion or retirement of a lesson. Confirming a provisional lesson, or retiring one, from its outcomes rather than from a person.Outcomes are association, not proof; a lesson that recurs may be wrong or may be right about a failure with two causes. c flags lessons for review and changes no status. d's replay shows knowledge confirm a with/without table and a verdict word, and still leaves the decision to the person.Twenty confirmed lessons whose replay verdict matched the person's decision, and a written rule derived from them.
A scratchpad, or a task.note tool. Something the engineer writes for itself during a turn — what it has read, what is left — kept by the host and put in front of the next turn.j gave the next turn the previous turn's own tool calls and results, bounded, from the host's transcript, and measured on two-turn tasks whether a "continue" still rediscovers what the last turn found. A scratchpad is the next step only if structured history does not remove that.The two-turn table in "What was measured" showing structured history still repeating most of turn one's reads.
A model from a second provider per task. A task's tier or its own choice naming a model from a provider other than the one configured in Settings.Landed in a. Settings keep an entry per provider, each turned on or off, and a model reference <provider>:<model> runs on that provider with its own key and address from a tier, a task or a conversation. A provider that is off is sent nothing; one that fails fails the task, with no fallback. The pickers that write such references are b.—
Two connections of one adapter. Two custom servers at once — a llama.cpp server on this machine and a hosted gateway, both through customServer() — each with its own address and key.Settings are keyed by adapter id, and an adapter is one connection. openaiCompatible already takes an id, so a second connection is a second adapter built with another id; what does not exist is a way for a person to add one, name it, and have it survive a restart, since the list of adapters is fixed when the launcher is compiled. It would take a stored list of connections, each { id, label, baseUrl }, turned into adapters when the AI layer starts, a key per connection under its own id, and a rule for an id that clashes with a built-in one.A person who needs two servers of the same kind in one launcher, and for whom OpenRouter's own preset (a) is not the second one.
A model list that streams each provider's group as it arrives. One stream route in place of ai.modelsList, sending a group per provider the moment it answers, so a picker fills provider by provider.c gave listing a five-second deadline of its own, a thirty-second fresh window, and the last list a provider gave when it does not answer in time. What is left is a first-ever list: with nothing kept, a provider that accepts the connection and says nothing still holds every picker for up to five seconds before its line says it did not answer. A stream fixes that, and costs a second route beside the operation (an operation answers once, and a . page calls the operation), a hook that merges groups and an order that stays the build's as groups arrive late, and the same stream in three panels unless it is shared.A person waiting on a first list for a provider that stalls, more than once.
The executor's notOffered sharing the kept model list. Before every builder turn the executor asks the task's provider for its whole list, under its own twenty seconds, to refuse a model that is no longer offered.It could use c's kept list, but only a fresh one: a stale list must never refuse a task, since the model may have come back. So it would save one list request per builder turn (up to TASK_MAX_TURNS, four, per task) and only when a panel read that provider within the last thirty seconds; turns are minutes apart, so in practice the first turn after Run with a panel open, and nothing after. Per saved request: one list round trip, tens of milliseconds for a local server, and up to twenty seconds before the turn starts for a provider that stalls.A run whose turns wait visibly on the list before they start.
Editing a task's text in the Backlog panel. Changing a plan's title, criteria or summary by hand rather than asking the engineer to replace it.a lets a person change a task's model, remove it or withdraw its intent, and nothing else: a plan is validated as a whole when it is written, and an edit form would need the same validator, the same slug rules and a rule for what an edit does to a task that has already run.A person asking twice to correct a plan the engineer would not correct.
An evaluation condition for planned against direct. The same multi-part request run once through the backlog (open, split, then each task in its own turn) and once as one direct turn, compared on working code, timeouts and tokens.b makes the engineer plan and c runs the plan; until a planned request can be carried to a release there is nothing to compare, and a condition that only measures the split would measure the part that is cheap.c's demo: one planned request carried through every task to a release on the configured model.
Parallel tasks. Running two tasks of one application at the same time when their locks do not overlap.One application has one source workspace and one candidate at a time, and a B model on one machine runs one turn at a time anyway. locks is recorded now so the question can be asked later from real plans.A hosted model fast enough that the queue, not the model, is what a person waits for, and plans whose locks show independent tasks.
Revert and continue with independent tasks. When a task fails twice, put the workspace back to where the task started and carry on with the tasks that do not depend on it.c's failure policy is stop, as workflows have it: the workspace is left as the attempt left it so the person and the engineer can look, and nothing is reset. Reverting is a git reset over a person's candidate, which is the kind of thing that must be asked, not done.A stopped run whose remaining tasks were independent and which a person restarted by hand, twice.
A stream for backlog progress. Push a run's progress — the task in hand, its last tool, a question waiting — to the Backlog panel as it happens.The launcher contract has no stream, and c did not add the first one: the panel polls launcher.intentGet every two seconds while a run is going, and the rail polls launcher.intentRunning. Each poll is a user run in the launcher's run store.A person missing a waiting question because of the two-second poll, or the run store growing noticeably from the polling.
A verdict as strong as its examples. A task is completed when an example per criterion passes on a verified build; nothing checks that the example would have failed before the change.The builder writes the examples it is judged by, so a trivially true example completes a task. c's split rules say a criterion is what a route returns or a page declares, never how the code is written, which makes it assertable; it does not make the builder's assertion meaningful. The Fail-before, pass-after row above is what would strengthen it: a criterion's example run against the current release too, and required to fail there.A completed task whose example passed before the task ran.
More matchers than $is. A range, a pattern, a length or "non-empty" inside match.d gave match one matcher, a value's kind, from a closed list, because each of these is a way for an example to pass on the wrong output: a pattern loose enough to accept the right string accepts a wrong one too. Every other $ key is reserved and refused, so adding one later changes no example already written.A task whose criterion is about a value's shape and cannot be proven by its kind plus a second step that reads the value back, seen in a real run.
A matcher that follows the route's output schema. "This output matches what the contract says the route returns", derived from the exported JSON Schema rather than written in the example.The host already validates a route's output against its schema, so an example asserting only that proves little a build does not; and a schema loose enough to be useful as a matcher would let an example pass on a wrong value of the right shape.A task whose output is large and variable enough that neither match with $is nor expect can be written for it, seen in a real run.
Fuzzy time in an example. "Within a minute of now", or "after the value an earlier step returned".d lets an example say a time is a number; it cannot say it is the right number. Relative time needs the check to know when it ran and a way to name an earlier step's output, which is a small language, and a flaky one on a slow runner.A task whose criterion is about when something happened, whose example passed with $is: number on a build that recorded the wrong time.
answeredBy on the gate's execution record. Who answered a question — a person on the tab, a person in the Backlog panel, a backlog run's standing answer, or the person's standing approval (a, Work without asking) — as a field of ExecutionRecord, written by the approver that answered.c's standing answer is recorded by the gate exactly as a person's answer is; only the launcher's log (one log event per answer) and the ai:intent-… caller say it was the run. The field is a core change to the gate and to every recorder, which c was not given.A person asking "did I approve this, or did the run?" of a run-store row, or a second kind of stand-in beside the backlog run — which a now is: the gate records the switch's answers as a click, and only the launcher's log says "the standing approval approved …".
Work without asking, per application. The switch on one application and not another, as a's first draft had it: <root>/apps/<appId>/standing.json, a row switch, standing <appId>.One launcher-wide switch is one thing to see and one to take back; the rule it answers by (standingCovers) already names the application, so a per-application file changes only where the answer is read.A person who wants one application kept guarded while the engineer works without asking on the others.
A standing approval that expires. Work without asking for an hour, or for today, then asking again by itself.A switch that turns itself off is a second thing to explain, and the Overview and top bar already say it is on.A person who turned it on for one session and found it still on days later.
A per-tool list the person edits. Choosing which of the six calls the switch covers — builds and previews but not edits, say.The six are what one change is made of; approving a patch without its build, or a build without its preview, still leaves a card at the step that matters.A person asking for one of the six to keep asking while the others do not.
The same standing approval for the mcp channel. An agent in another program working without asking.Two hops of trust, as external over MCP is; and elicitation is where an MCP client's own approval belongs (see Approval by MCP elicitation).The elicitation row done, and a person asking for it.
A data snapshot recorded when a check case opens. Today a check case records none, so its replay runs on a copy of the application's data taken the first time it is replayed, and the manifest says from: live.A snapshot per failed check costs a database copy each time an example fails, and no replay had been run to show it mattered.A replayed check case whose verdict changed with the data it ran on.
Outcome-weighted ranking of lessons. Serving a lesson more often because its servings were credited resolved.Attribution: an outcome is association, not proof. d watched two unrelated lessons credited resolved beside the one that applied, and a build error that names its own repair makes every lesson served beside it look useful. A weight learned from that would rank noise.A replay design that attributes a repair to one lesson (with and without, per case), run on enough cases that the verdict words are stable. Then a factor in findLessons' product that is positive, never zero, and never added.
Embeddings for lessons. sqlite-vec or any vector search beside the full-text match.The trigger for these is a measured retrieval miss caused by wording, not a corpus size. a chose lessons by fixed rules and records why each was served; nothing yet shows a lesson missed because its words differed from the task's.A replay in which a lesson that would have helped was not served, and the search event's tokens show the miss was a matter of wording.
why and links in the Knowledge panel. Showing, per turn, why each document was there, and a task's links.a records both (search.why, links) and the CLI prints the counts; a panel for them is a design task of its own.A person asking why a lesson was served, or a replay whose explanation needed the index.
locks checked at plan time. Refusing, or asking about, a locks entry that is not a file of the workspace when the planner writes it.a skips such entries when it builds the index and reports them; on this machine's store every task's locks was empty, so there is nothing yet to check.Planners writing locks at all, and the skipped list showing they name files loosely.
Links kept incrementally. Adding and removing rows as events are written, instead of rebuilding the whole table.A full rebuild over this machine's store took under ms, well under the ms that would make it worth the correctness risk of two paths.A rebuild measured over ms on a real store.
Activation's examples on data larger than the disk holds twice. Activation copies the migrated data once more, to data-check/, and runs the acceptance examples there, so for a moment the disk holds the live data, its snapshot, data-next/ and data-check/. Nothing checks for room first and nothing skips the copy.e made activation run the examples where the preview runs them, on a copy that is thrown away, because an activation that did not run them is not one. The preview already pays the same copy on every start, and the journal records how long each took (checkCopyMs).An activation that failed for lack of disk, or a checkCopyMs long enough that a person waiting on Activate noticed. Then check free space against the data's size before the snapshot, and say so before anything is copied.
Accepting a changed example from the panel. Since b a task that changes the steps of a finished task's example is not completed ("The example <id>, from a finished task, was changed."), and nothing but revising the plan lets an honest change through: a later task whose output gains a field an older expect must follow fails, and the person has no control that says "this change is right".b's Step found no case to justify it: across completed tasks on this machine, no later task changed a finished task's example, honestly or otherwise. A control built before the need is a control that trains a person to click it.The first real task stopped by the rule whose change was honest, recorded in a report. Then: a panel action on the failed task that accepts the new steps, writes the new hash on the older criterion's row, and says so in its history.
A release that does not load, caught by the build. Loading the host bundle once in a child as a build stage, or checking the workspace for an identifier used at module scope and declared nowhere, so a release that builds but cannot start is a build problem and not a preview failure.a's evidence: on -- background-remove built release 17470f1f whose bundle held var probeNamedImports = [useBroapp, useBridge, …], and it failed only when a preview's child imported it. a made that failure say why, which ended the search it cost; catching it earlier costs a child start on every build (the launcher never imports a release's host.js, so the load has to be a child's), or a type check of the workspace, which the compiled launcher does not carry. Neither is small, and a start failure now reads as one.A second release that builds and does not load in real use after a, or a build that already starts a child for another reason.
Telling the builder how an application's own UI calls its operations. The builder wrote the probe above because it did not know which of broapp/react's hooks a component in src/ui/ uses to call a route, and tried to find out by naming them all at module scope.One case, and the workspace topic already says what src/ui/ is for. A paragraph written for one case is a guess at what the next builder lacks.A second turn that writes code to discover an API rather than reading it, or a request that needs an application's own component to call a route.
Stopping a run at the daily budget. Refuse the next turn, or stop the run, once today's spend passes budget.day in prices.json.a shows the budget and never enforces it. What a run has cost is a floor whenever a turn was cut short, a model with no price has no cost at all, and a stop in the middle of a task leaves half a change: a limit that fires on a figure that is not the whole, or not at all for an unpriced model, would stop the wrong runs and miss the right ones.A person who set a budget and was surprised by a day's spend, and prices set for every model the launcher runs, so the figure a stop would act on is known.
Cost by day or week. Spend over days as well as "today", from the usage table, which already keeps every turn with its time.a answers what the day has cost and what the run in hand has; nobody has asked what last week did. A history needs a place to be read (a screen), a retention rule for usage, and prices that were right for the days it covers, which a price corrected today rewrites.A person asking what a week of backlog runs cost, with prices set for the models that ran it.
More than one run at a time. Two backlogs building at once, on two applications, each with its own standing answer and its own row in the overview.The executor runs one run per launcher by design (c), and a's overview has one running block for that reason. Two runs share one AI provider, one Settings model and one person to answer them; two turns thinking at once on a local model halve each other's speed.A hosted provider in daily use, and a second application with a backlog that waits on the first often enough to matter.
Charts, or the Overview over days. A line of spend or of tasks completed per day on the Overview, rather than today's figures alone.b draws what one read says now: what needs the person, the run in hand, today. A chart needs a history to draw (the usage table keeps one; "Cost by day or week" above is the read it would need) and a question somebody asks of it; a chart that answers nothing is decoration on the first screen."Cost by day or week" built, and a person looking for a trend the day's figures cannot show.
A compact strip above the chat. The Overview's summary figures and its attention count in one line over the conversation, so the chat view says what needs the person too.The rail's Overview button already carries the count and the waiting mark while the chat is the view, and the title and the alerts carry the rest; a second place for the same figures is a second thing to keep in step.A person who works in the chat for long stretches and misses something the rail's count did say.
Arranging the Overview. Dragging, reordering, hiding or pinning its blocks.The mockup fixes the order, and a screen that moves is a screen a person has to learn twice. Nothing about a single person's launcher needs a second layout yet.Two people, or one person with two kinds of work, asking for different first screens.
Move an existing application's workspace from the launcher. Copy the workspace to a new folder, verify it byte for byte, repoint location.json, and only then offer to remove the old copy.a lets a person choose where a workspace is made and locate one they moved themselves; moving it for them is a copy across volumes with a verification step and a decision about the old copy, which is its own prompt.A person asks to move one; the copy-verify-repoint order written down with what happens when each step fails.
import --at. Import a workspace into a folder the person chose rather than the launcher's own.import copies into the default place, and an application that already has a pointer is refused by it (a).A developer who imports asks for it.
Show in folder. A row action that opens a chosen workspace in Finder or Explorer.It spawns the system's file manager with a path, and b's folder dialog decides how that is done safely first.b lands.
A default location in Settings. Where New application proposes to put a workspace when the person does not choose.With no folder given nothing changes, which is the promise a keeps; a default elsewhere changes it for everybody.People choosing the same folder every time.
Relocating a default workspace. locate for an application whose workspace is <root>/apps/<appId>/source/.locate only repoints a chosen workspace (a); moving a default one out of the launcher's folder is the move above.The move above.
A release id that does not depend on where the workspace is. The same files built at two paths gave two release ids (a's by-hand run, after locate).Release identity is the hash of the built bytes, and the bundler writes the source's absolute path into them. It was always so; before a a workspace's path never changed, so nobody could see it. Nothing breaks: a moved workspace's next build is a new release with the same behaviour.Somebody compares releases across machines, or a moved workspace's history confuses a person; find what embeds the path first.
Drag a folder onto the form. Drop a folder from Finder or Explorer on Where it lives to choose it.A browser does not hand a page a dropped folder's full path, only its name and its files; getting the path means reading the drop through the host, which is a second way in for the same string the folder window already gives.People asking for it after using the window.
A folder browser drawn in the page. Choose a folder from a tree the launcher's page draws, instead of the system's window.It needs a route that lists a person's directories to a page, which the system's own dialog makes unnecessary (b).A platform where no dialog exists and typing a path is not enough.
Watching a chosen folder. Noticing at once that a workspace went away or came back.State is computed whenever it is asked for, so a folder that returns is seen on the next list, build or turn without a restart; a watcher adds a platform-specific moving part for a few seconds' difference.A person who was surprised by the delay.

Findings from the build, folded in

Blocking the loop

FindingWhereWhat to do
The engineer reaches edits and stops before a build. In c the model made three correct source.edit calls in ms, then spent twenty minutes planning the host and contract changes and ended the turn without calling candidate.build. Hunk size is no longer the obstacle; the plan is.cMeasure it against a hosted model before designing around it. If it persists: a shorter loop — build after each coherent edit set, and let a failed build be the next instruction — rather than a longer plan. In-turn guidance landed in b (every edit result names candidate.build, and warns from the third unverified edit); measured in d. b's rerun found a second cause: the turn ended after exactly eight model steps, the AI layer's DEFAULT_MAX_STEPS, which the launcher's createAi does not override. It had tool calls, two landed edits, and no closing text. c's turn, with calls and ending right after an edit, fits the same cap. d should measure with a raised maxSteps before it attributes the stop to the model. c raised it: the launcher's tab passes maxSteps: 40 (LAUNCHER_MAX_STEPS); every other createAi keeps eight. After d the host took the steps over: candidate.cycle patches, builds, and on a pass previews and checks in one call, returning located problems and a same-failure signal; each action still asks. Whether it moves completion is unmeasured on a real model.
A restarted child is unreachable from the tab that was open. serve restarts a crashed child (three times a minute), and the new child has a new port and a new one-time launch token. Today the launcher prints the address.The launcher knows the new address. Give the page a way to be handed one — the same route the launcher tab already uses to open an application. The preview half is covered (a): the candidate survives a launcher restart, and launcher.previewStart starts its preview again and opens it. A crashed application child is still only printed.
A killed launcher leaves a stale control file. launcher.json is removed by an exit handler, and on Windows a terminated console process runs none. An MCP client then reads a dead port; it reports "the launcher is not running", correctly, after a refused connection rather than from the file.The file already carries pid. Check that the process is alive when reading it, and remove a file whose process is gone.
The tab does not notice a promotion until it reloads. autoapp.viewsGet is fetched once when AutoappView mounts.Refetch after a promotion, or make views a stream. Small either way.

Correctness and housekeeping

FindingWhereWhat to do
MigrationSpec.checksum is validated but never computed or verified against the migration's content. A migration can be edited after the fact and nothing notices., Compute it in the build from the migration's source and check it before running one.
activate from the CLI cannot drain an application another process is serving. It finds children through its own supervisor, and a separate broapp-autoapp activate has none. It works because the manual sequence stops serve first.The control connection now exists. Route activate through a running launcher when launcher.json is present, and keep the standalone path for when it is not.
data-prev-* directories, snapshots and previews accumulate, and runs.sqlite grows with ordinary use — every user call is a run., The prune row above, plus a retention window for run records. Both need the same "list what would go, require --yes" shape.
listReleases hashes every release on every call, and launcher.appsList reads every release directory on every call., bAn mtime-keyed cache once releases accumulate. Cheap today; do not do it before it matters.
Envelope.effectHint may be dead weight. It exists so a core Broapp contract written before effects existed still classifies correctly. Every Autoapp route must declare an effect, so no Autoapp application uses it.Keep while core contracts may omit effect. Delete when effect becomes required in defineContract — which is a breaking change for broapp, not for Autoapp.
checkDependencies accepts a dependency resolved from a node_modules above the workspace. That is how the examples in this repository build at all, and it means the vendored-directory guarantee is about the built release rather than about the workspace.Leave as is for a workspace inside a monorepo; consider a --strict build that requires every dependency under source/node_modules for an application imported from outside one.
An application whose package.json uses workspace:* cannot vendor its dependencies. import runs the install and reports that it could not; the build then resolves upward and succeeds anyway.Either rewrite workspace:* to a concrete version at import time, or say plainly that importing from inside a monorepo is a developer path and not the shipped one.
notes.backup writes a file, is classified write, and is therefore offered over MCP. Whether a file-writing operation should be reachable from another program at all is a question the effect vocabulary does not ask.Consider a fourth effect, or a capability-derived filter on what MCP offers. Do not widen external: its meaning is load-bearing in the preview policy.
autoapp.viewsGet returns the whole specification on every page load, and the renderer has no list virtualisation. notes.list is bounded at ,autoapp.viewsGet rows by its contract, and nobody has measured ,autoapp.viewsGet rows in one DOM.Per-page views and virtualisation, together, when a release has enough of either to notice.
Most of a specification still drops a key it does not know. a closed an acceptance example, its steps and a step's view, as capabilities and fails already were. Still open, parsed by s.object, which drops unknown keys: the specification's top level, manifest with its runtime and entry, contract and each exported route, each migration, each workflows entry, the grants file, every object in the view specification (page, source, component, column, field, action), and preview.try's step, which is a tool input rather than a specification. A misspelt key in any of them is read as absent.aClose each where a dropped key can change a meaning, the way a did: the view specification first, since the engineer writes it by hand, then the manifest. The migration and route objects are written by the build, not by hand, so they matter least.
Valid inputs for the other structured tools, and where a migration's SQL goes. c held the four shown examples to examples/notes (each one applies). No other tool has one: preview.try and intent.open take nested objects and would be the next candidates if a builder is seen refusing them twice. And no text the engineer is shown says where a migration's SQL lives. The workspace topic lists migrations in autoapp.json and says src/host/app.ts exports migrate, and all three applications keep the SQL in MIGRATIONS in src/host/db.ts. Only the source.edit example now shows that.cAdd an example for a tool only after a repeated refusal of it is seen in a real turn. Add a line to the workspace topic once a builder is seen putting a migration's SQL somewhere else.
--tasks and --conditions on knowledge evaluate. The options exist on evaluate(); the command does not expose them, so measuring one cell means running all of them (j: about hours on a local B model).j, dAdd both flags, validated against EVALUATION_TASKS and CONDITIONS, when the next measurement needs one cell alone.
Checkpointing an evaluation. A stopped evaluation leaves runs.jsonl, but the table is written only once every cell has run, and nothing resumes. Both d attempts and both j attempts were stopped before a table existed.d, j, dRebuild the table from runs.jsonl and resume from the cells it lacks.
Counterbalancing condition order. Cells run in one fixed order within a run, so a model or machine that drifts over hours touches the conditions in the same order every time.dRotate the condition order per run; record the order in runs.jsonl.
An executor-level evaluation. The evaluation measures single chat turns; the backlog run (c onward) is where the builder now works, with its own retries, verdict and host build, and no table measures it.dA harness that runs a planned intent through the executor on a fixed seed, measuring completion per task and attempts.
Relabelling "working code" and "workflow completed". Both are defined in the table's preamble, but the names read as more than they are: an example that asserts little can pass either.dRename them to what they check once "A verdict as strong as its examples" is done, and not before.
Narrowing the notes-archive request. The /c request asks for tags and an archive action and a workflow at once, which is two or three tasks; most of its runs time out under every condition.d, dSplit it, or replace it with the part a single turn can finish, and say in the table that its numbers are not comparable with earlier ones.
Replay rows say nothing about incomplete tokens. Since d a replay turn that timed out records the steps that completed, not zero, in replays.input_tokens/output_tokens, and the verdict table prints it like a total.dAdd a tokens_complete column to replays and print a floor as a floor, the way the evaluation now does.
Bounding the context of the turn that is running. e bounds what a later turn is given of an earlier one. A turn of thirty calls still carries all thirty calls and results into every step of its own, until the provider's window or the twenty-minute limit ends it. Per-step preparation would drop or elide the oldest tool results as the turn grows.eThe paper behind the series found that managing a long turn's context mainly prevents overflow. So the trigger is a turn seen to fail on the provider's context window, recorded in a report, not a wish to make turns better.
Line ranges on source.read. A read returns the whole file (up to ,source.read bytes), so a turn that reads four large files carries all four into every later step and into its transcript. A from/to pair would let a builder read the part it needs.eThe same paper finding: this mainly prevents overflow. Start when a turn is seen to overflow on reads, or when the transcripts show most of a turn's characters are file contents it did not use.
Keeping a suffix of a transcript over MAX_TRANSCRIPT_CHARS. A turn's transcript over ,MAX_TRANSCRIPT_CHARS characters is not written at all (j), so the next turn gets its words only. j's largest stored turn was ,, so the cap has not been hit.eThe first transcript refused for size, logged. Then keep its newest complete groups, as e's expansion does, rather than nothing.
Reasoning is not bounded in an expanded turn. j bounds a call's input (text,reasoning) and a result's output (,reasoning), and keeps an assistant message's reasoning and text parts whole. Re-run through e, the -call notes-archive turn kept only its last four calls. The group before them holds , characters of reasoning, which alone outgrew the room left.eBound reasoning like an output (the head, and how much was left out), or drop it from expanded history. That changes HISTORY_LIMITS' meaning, which e fixed as a separate experiment. Measure what a builder does with and without it first.
**A turn that repeats a successful call.** f ends a backlog turn refused four times the same way; a turn that reads the same file over and over, each read succeeding, is ended by nothing but the idle and time limits, and noteRead only says so in the read's result.fA backlog turn seen spending its limit on repeated successful reads with the workspace unchanged, recorded in a report. Then count them the way refusals are counted, with the result's own note as the second step.
workflowRun returns only when the run ends, so a workflow waiting on a person holds an operation open for as long as they take.Make it a stream. The operation's signal already cancels it, so the semantics are settled; only the shape changes.

Done after all, and removed from this list

  • A Lessons panel in the launcher tab. The Knowledge panel (k) shows the

turns with what each delivered and what came of each serving, the lessons with provenance, servings and replay evidence, and the cases; and it confirms, retires, writes and replaces a lesson through review.ts, the functions knowledge confirm and knowledge retire share.

  • A remove command. Creation made applications cheap and nothing could

take one away again. launcher.appRemove and the remove command move an application's directory to <root>/trash/ in one rename, after the person types its id or passes --yes; nothing removes while it serves, the launcher never empties the trash, and there is no engineer tool. i.

  • A patch-shaped source.change. Prompt measured a B local model spending minutes composing a whole-file rewrite. source.edit with find-and-replace hunks landed in b.
  • Whitespace-tolerant hunk matching. b lost three of six edits to leading whitespace in text the model had just been given. c matches exactly first, then line-wise with indentation ignored, and reports which. Three edits for three in the following demo.
  • An apps.list tool. The engineer could not discover what existed and had to ask. Added in c, and called first, unaided, in that demo.
  • The gate's -second confirmation window against a model that thinks for ten minutes. The launcher's gate and its AI layer now use s, and every question carries askedAt and expiresAt so the countdown is visible.
  • Release identity covering only page, host and contract. A change to views, migrations, acceptance or capabilities alone did not change the identity, so a person could add an acceptance check that could never reach a release. Widened to the whole specification in b.
  • instanceof across the release boundary. A release bundles its own broapp; the child runtime carries another. Every check is a shape check now, and tests/autoapp-boundary.test.ts fails the suite rather than a review.
  • The offline tiers, documented as untested. Now three cases in tests/autoapp-offline.test.ts on three platforms, with the evidence and the limits in packaging.md.

What was measured

QuestionAnswer
Build iterations the engineer needed per change, and where it stalledPrompt : zero builds — the model never emitted a whole-file source.change for the large request ( minutes composing). b: one successful edit in six attempts, three lost to whitespace, two to the s window; still no build. c: three edits, three successes, ms — then twenty minutes of planning and the turn ended without a build. The obstacle moved from the tool's shape to the model's plan.
Whether a promoted workflow was used again after the demo sessionNot yet. One promotion, used twice inside the session that made it (prompt ). No data after it.
Whether any approval was declined, timed out, or mismatched in real useAll three happened in the demos: a declined delete (), two questions that expired under the old s window (b), and a mismatch check that refuses a grant naming a release the person was not shown (). None outside a demo — nobody is using this yet.
Binary size: launcher versus a Notes binaryLauncher . MB, Notes . MB, both darwin-arm64 with --compile --bytecode --minify. The launcher costs about MB more than an application; the rest of both numbers is the Bun runtime. Per-target sizes are in packaging.md.
Time from "ask" to "activated" for the Notes change, wall clockNever reached by a model. The closest is b's minutes from the first message to a successful edit, and c's minutes to three edits and no build. Activation itself, driven by hand, is seconds: prompt 's manual run went from build to a serving new release with the previous release's data intact.
Whether orientation, evidence and in-turn guidance move the stall (b, d)b's rerun of the c request: calls before the first edit (c: ), no apps.list, two edits landed; the turn ended at the AI layer's -step cap, raised to in c. The four-condition evaluation then ran three times a cell on qwen3.8:27b-mlx (--; two build sessions overlapped nine of the thirty-six runs, so their times are not comparable, their outcomes are): working code in of runs at baseline, with orientation alone, with orientation and curated facts, with learned lessons; timeouts , , , . Reads no document offered fell from about a run to about , calls to the first edit on the archive task from . to ., and tokens per finished run by up to twelve times. The path cuts the cost of a turn and does not yet raise completion on this model; orientation without facts or lessons made things worse.
Whether a "continue" turn rediscovers what the last turn found, with text history and with the last turn's own tool calls (j)Not measured to three runs. Two attempts on qwen3.8:27b-mlx (--/) were stopped: the first at of runs for a bug it found (a cut tool input sent as a string, which Ollama refuses; fixed), the second at of to free the machine. One run a cell, turn two under text history repeated most of turn one's reads ( of , , of ); under structured history repeated reads fell in of pairs, reads before the first edit did not reliably fall, and no structured run added working code. Transcripts of about calls or more are ,qwen3.8:27b-mlx–,qwen3.8:27b-mlx characters after bounds and so stay text. Details in the j report; the three-run table is still to take.
Whether a distilled lesson helps on replay (d)One lesson (a confirmText fact), replayed three runs an arm under model contention: qwen3.8:27b-mlx/ passed with it, qwen3.8:27b-mlx/ without — verdict unrelated; the build error already names the missing field. The distiller on qwen3.8:27b-mlx gave qwen3.8:27b-mlx valid answers in questions; the rest were unparsable, over s, or over the reasoning limit.

These decide whether the core promise — software that keeps adapting to its owner — holds up enough to invest in containment, custom views, or a second example. On this evidence the loop is real end to end when a person drives it, and the part that is not yet real is a model driving it unaided from an ask to an activation.

Edit this page on GitHub