From 0c22895e002db802eb5fc98442401639e55be0be Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 08:46:54 +0000 Subject: [PATCH 01/16] Style pass: re-voice the root, worktrees, and cloud-driver specs to the user-story axis Upstream sdd.md now demands flows explained from the user-story perspective for a technical-PM reader. These three serve as the calibration exemplars: User Stories name what the user does and gets, flows open from the user's action before the mechanism, coined terms are glossed at first use, and the git mechanics that carry constraints move fully into Rationales. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- SPEC.md | 2 +- .../the-framework/src/driver/cloud.SPEC.md | 20 ++++++++++------ packages/the-framework/src/worktrees.SPEC.md | 24 ++++++++++++------- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/SPEC.md b/SPEC.md index 9d06c5540..04aa2ad29 100644 --- a/SPEC.md +++ b/SPEC.md @@ -12,7 +12,7 @@ Autonomous AI programming: humans make the important decisions while The Framewo ## Flows -- The product is a local daemon plus a dashboard. The user registers repos, and from then on coding agents (Claude Code today, other CLIs pluggable behind the same driver interface) work on them: each agent gets a throwaway copy of the repo, does its work, and hands the result off as a pull request. +- The product is a local daemon plus a dashboard. The user registers repos, and from then on coding agents work on them: each agent gets a throwaway copy of the repo, does its work, and hands the result off as a pull request. Claude Code does the coding today; other CLIs can plug in behind the same adapter, the *driver*. - The human's job shrinks to decisions: answer the questions an agent parks on, accept or reject proposed tickets, review PRs. Everything else — picking the next task, triaging, planning, fixing red CI, merging on green — the daemon does by itself when nobody is at the keyboard, as long as the account's quota allows it. - The driver is a black box: The Framework prompts it, lets it run a full turn, then reads the code and the turn's final message. It never micro-manages individual tool calls, and the CLI keeps its own subscription login — The Framework adds orchestration, not another AI bill. - Two satellites complete the family: a browser extension that bridges claude.ai cloud sessions back to the daemon, and the product's website. diff --git a/packages/the-framework/src/driver/cloud.SPEC.md b/packages/the-framework/src/driver/cloud.SPEC.md index 6068fbefc..d43523424 100644 --- a/packages/the-framework/src/driver/cloud.SPEC.md +++ b/packages/the-framework/src/driver/cloud.SPEC.md @@ -1,14 +1,20 @@ A driver that hands the whole task to Claude Code on the web: it starts a real cloud session on the user's own account and gives back the link where the work continues. +## User Stories + +- The user hands a task to Claude Code on the web and gets back the link where the work continues on their own account. +- The user's cloud session works on the real repository — it clones from GitHub and can push and open a pull request. +- The user later finds the session's work adopted onto the run's dashboard record, even though the session named its own branch. + ## Flows -- One agent, one cloud session — ever. Every prompt after the first just reports the hand-off that already happened, without spending another session. +- One agent spends one cloud session — ever. Every prompt after the first just reports the hand-off that already happened, without spending another session. - The agent ends at the hand-off: a cloud session offers no way to read status, replies, or output back — only its link, plus a command to pull the session back locally. That the agent ends there is a fact of the web location, not something this driver declares. -- The project root is trusted for the CLI before the hand-off, so the CLI's interactive trust question — which a background run could never answer — does not fire. A dialog that appears anyway (the write failed or was rejected) still fails fast with the manual fix named instead of timing out with nothing to show. +- The project root is trusted for the CLI before the hand-off, so the CLI's interactive trust question — which a background run could never answer — does not fire. If a trust dialog appears anyway, the start fails fast and names the manual fix, instead of timing out with nothing to show. - Nothing the user typed can ever reach a shell as syntax. -- The session it creates is repo-bound — it clones from GitHub and can push — never a silently uploaded local bundle whose work could never leave the VM; the CLI's nonessential traffic is switched off for the invocation to keep it so. -- Before the hand-off, an anchor — an empty commit on top of HEAD, unique to this run and minted without moving any branch — is pushed to origin under the agent's own slash-free id, and the session is told to clone at it; the branch the cloud session works on (a name of the cloud's own choosing) descends from that anchor, and that ancestry is how the daemon later recognizes which branch is this run's. -- A hand-off whose anchor cannot be minted or pushed goes ahead with no ref and says so, naming `--teleport` as the recovery path; such a run is never matched to its branch. +- The session is repo-bound: it clones from GitHub and can push, never a silently uploaded copy of the local checkout whose work could never leave the session's VM. The CLI's nonessential traffic is switched off for the invocation to keep it so. +- Before handing off, the run leaves an anchor on the repository — a marker commit unique to this run — and the session clones at it. Whatever branch the session then works on descends from that anchor, and that ancestry is how the daemon later recognizes which branch is this run's. +- If the anchor cannot be created or pushed, the hand-off still goes ahead with no starting point named, says so, and names `--teleport` as the recovery path; such a run is never matched to its branch. ## Rationales @@ -16,8 +22,8 @@ A driver that hands the whole task to Claude Code on the web: it starts a real c - The hand-off ending lives on the location rather than on the driver so later phases never mistake the driver's own summary for the agent's answer. - Trusting the project root on the user's behalf is sound because starting a web agent is itself the user's trust decision, and worktrees inherit the root's trust, so one grant covers every agent workspace. - With nonessential traffic disabled, the CLI's server-side bundle experiment reads off, so a failed GitHub-App preflight falls through to a repo-bound session instead of a silent local-bundle upload (#1320, anthropics/claude-code#81776). -- The anchor is an empty commit rather than HEAD itself so it is unique to this run and mints without moving any branch. -- The ref is minted slash-free and handed over explicitly because the CLI's default revision pin is the current local branch — which an agent workspace's local-only branch fails — and a slash-carrying ref never resolves on the cloud side even when pushed (anthropics/claude-code#87235). +- The anchor is an empty commit on top of the checkout's tip, rather than the tip itself, so it is unique to this run and is minted without moving any branch. +- The anchor's name carries no slash and is handed to the CLI explicitly, because the CLI's default is to pin the current local branch — which an agent workspace's local-only branch fails — and a slash-carrying name never resolves on the cloud side even when pushed (anthropics/claude-code#87235). ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/worktrees.SPEC.md b/packages/the-framework/src/worktrees.SPEC.md index 70f720871..32bb65a2b 100644 --- a/packages/the-framework/src/worktrees.SPEC.md +++ b/packages/the-framework/src/worktrees.SPEC.md @@ -1,16 +1,22 @@ Cleans up the per-agent checkouts a project retains — one implementation behind the dashboard's buttons, the teardown and the automatic sweep, so none of them can drift. +## User Stories + +- The user removes an agent's checkout — or lets the automatic sweep reclaim idle ones — and can always recover the work afterwards. +- The user deletes an agent for good: its records leave the dashboard, while its branch and commits survive. +- The user learns why a checkout stayed whenever cleanup could not reclaim it. + ## Flows -- **One rule: only what is on the remote may go.** Removing a checkout commits whatever it is still holding to the agent's branch, pushes that branch, and deletes the checkout only once the remote has it. -- A repo with nowhere to push keeps every checkout. -- A session set to publish nothing (`handoff: local`) keeps its unpushed checkout; that decision comes before anything commits, so its checkout goes only from a clean tree on a tip already on the remote, where removing it publishes nothing. -- A web run's checkout goes without a push once it provably holds nothing — a clean tree whose tip is inside what the hand-off already pushed; any doubt falls back to the ordinary commit-push-remove rule. -- A record that cannot be read keeps the checkout too, and a later pass retries; a record that was never written is a boot death and takes the ordinary commit-push-remove path. -- One failure mode, and it is legible: the push did not land, so the checkout stays and the reason says why. -- Deleting an agent is the other thing entirely: its archived records leave the dashboard for good and uncommitted work is discarded with the checkout — but the branch and its commits stay. -- Removal and deletion both refuse while the agent is live. -- The prune sweep offers every non-live checkout to the same rule and reports each one it could not reclaim, so a checkout that stays is always accounted for rather than silently kept. +- When the user removes a checkout (or the sweep reclaims one), the work is made recoverable first: whatever the checkout still holds is committed to the agent's branch, the branch is pushed, and the checkout is deleted only once the remote has it. **Only what is on the remote may go.** +- In a repo with nowhere to push, every checkout is kept. +- A session the user set to publish nothing (`handoff: local`) keeps its unpushed checkout. That decision is checked before anything commits, so such a checkout goes only when it is clean and its work is already on the remote — removing it publishes nothing. +- A web run's checkout goes without a push once it provably holds nothing: its tree is clean and its work is already inside what the hand-off pushed. Any doubt falls back to the ordinary commit-push-remove rule. +- A checkout whose session record cannot be read is kept, and a later pass retries. A record that was never written means the session died at boot, and the ordinary commit-push-remove path applies. +- There is one failure mode, and the user sees it: the push did not land, so the checkout stays and the reason says why. +- When the user deletes an agent, the deletion goes further: the agent's archived records leave the dashboard for good and uncommitted work is discarded with the checkout — but the branch and its commits stay. +- While the agent is live, both removal and deletion refuse. +- The automatic sweep offers every non-live checkout to the same rule and reports each one it could not reclaim, so a kept checkout is always accounted for. ## Rationales From b748fa6b786b36a4c9058d37e1f1ebab45eb7ed6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 08:56:51 +0000 Subject: [PATCH 02/16] Style pass: re-voice src daemon, config, events, and cloud specs to the user-story axis User Stories added where behavior is user-visible (control, daemon, config layers, data branch, Discord, events, install, layout, handoff-level, cloud-work); flows open from the user's action before the mechanism; coined terms glossed inline; two-read sentences split. Pure plumbing keeps its bird's-view framing with the surfaced effect named. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- .../the-framework/src/cloud-scratch-refs.SPEC.md | 4 ++-- packages/the-framework/src/cloud-work.SPEC.md | 7 ++++++- packages/the-framework/src/config-layers.SPEC.md | 6 ++++++ packages/the-framework/src/config.SPEC.md | 6 ++++++ packages/the-framework/src/control.SPEC.md | 12 ++++++++++-- packages/the-framework/src/daemon-runtime.SPEC.md | 10 +++++++++- packages/the-framework/src/daemon-services.SPEC.md | 10 +++++++++- packages/the-framework/src/daemon.SPEC.md | 6 ++++++ packages/the-framework/src/data-branch.SPEC.md | 9 ++++++++- .../src/discord-credentials-store.SPEC.md | 5 +++++ .../the-framework/src/discord-credentials.SPEC.md | 6 ++++++ packages/the-framework/src/driver-cli.SPEC.md | 5 +++++ packages/the-framework/src/events.SPEC.md | 8 +++++++- packages/the-framework/src/fake-script.SPEC.md | 4 ++++ .../the-framework/src/framework-gitignore.SPEC.md | 2 +- packages/the-framework/src/handoff-level.SPEC.md | 5 +++++ packages/the-framework/src/install.SPEC.md | 9 ++++++++- packages/the-framework/src/jsonl-tail.SPEC.md | 2 +- packages/the-framework/src/layout.SPEC.md | 5 +++++ 19 files changed, 109 insertions(+), 12 deletions(-) diff --git a/packages/the-framework/src/cloud-scratch-refs.SPEC.md b/packages/the-framework/src/cloud-scratch-refs.SPEC.md index 401fb1e13..b41e11a12 100644 --- a/packages/the-framework/src/cloud-scratch-refs.SPEC.md +++ b/packages/the-framework/src/cloud-scratch-refs.SPEC.md @@ -2,10 +2,10 @@ Deletes the dead refs web runs leave on origin — the `cloud-*` ref a hand-off ## Flows -- A web run pushes a `cloud-*` ref for the cloud session to clone at; the session then works on its own branch and opens its PR from there, so nothing ever consumes the ref again. A web run's own branch never reaches origin — its checkout is reclaimed without a push once the cloud session has what it needs — while a local run's branch does, and is swept only once its work has landed. +- A web run pushes a `cloud-*` ref for the cloud session to clone at; the session then works on its own branch and opens its PR from there, so nothing ever consumes the ref again. A web run's own branch never reaches origin: its checkout is reclaimed without a push once the cloud session has what it needs. A local run's branch does reach origin, and is swept only once its work has landed. - The daemon sweeps hourly; the driver that pushed a ref never deletes it itself. - A ref goes only when every gate clears: it is about a day old, its commits are already on the default branch, it has no open pull request, and its agent is not one the daemon is still running. -- The hand-off anchor is the one tip the default branch never absorbs — an empty commit no merge ever lands — so it clears the work gate its own way: a tip that changes nothing against its parent, on a parent that landed, holds no work. +- The hand-off anchor — the marker commit a hand-off pushes as the `cloud-*` ref — is the one tip the default branch never absorbs, since no merge ever lands an empty commit; it clears the work gate its own way: a tip that changes nothing against its parent, on a parent that landed, holds no work. - A run branch's age is in its name; a `cloud-*` ref's is not, so the sweep remembers when it first saw one and ages it from there. - Conservative and quiet: anything unprovable simply stays for the next pass, and only actual deletions (and failures) are announced. diff --git a/packages/the-framework/src/cloud-work.SPEC.md b/packages/the-framework/src/cloud-work.SPEC.md index 1df08e89e..ecee8124f 100644 --- a/packages/the-framework/src/cloud-work.SPEC.md +++ b/packages/the-framework/src/cloud-work.SPEC.md @@ -1,10 +1,15 @@ Adopts the branch a cloud session actually worked on: each settled web run is matched to the `claude/*` branch that grew out of its hand-off, and that branch — and its pull request — is recorded on the run. +## User Stories + +- The user hands a task to Claude Code on the web and later finds the run's dashboard row carrying the branch the cloud session actually worked on, and its pull request. +- The user armed the run to open a pull request; when the session never opens one, the daemon opens the draft for it. + ## Flows - A web run hands the task to claude.ai and ends; the cloud session does the work on a branch of its own naming, never the branch the run was born on. - The match is exact, never guessed: the hand-off pushed a commit unique to the run for the session to clone at, so the session's branch — and only it — descends from that commit. A run matching no branch (the session has not pushed, or never will) or more than one is simply asked again next pass, and a run past the window (two days) stops being asked about. -- What gets recorded, as one commit on the data branch so every machine learns it: the branch, and the pull request the session opened for it. A run that was armed for a PR the session never opened gets its draft PR opened by the daemon — unless the branch carries nothing beyond the hand-off itself, or the session's pull requests could not be listed that pass. +- What gets recorded, as one commit on the data branch (the dedicated branch the framework's own records live on) so every machine learns it: the branch, and the pull request the session opened for it. A run that was armed for a PR the session never opened gets its draft PR opened by the daemon — unless the branch carries nothing beyond the hand-off itself, or the session's pull requests could not be listed that pass. - A later daemon pass patches the run's record, the same way a late-opened PR already is. - Adoptions and failures are said out loud; a run still waiting is not. diff --git a/packages/the-framework/src/config-layers.SPEC.md b/packages/the-framework/src/config-layers.SPEC.md index 21a0f7548..e2d40089b 100644 --- a/packages/the-framework/src/config-layers.SPEC.md +++ b/packages/the-framework/src/config-layers.SPEC.md @@ -1,5 +1,11 @@ Resolves an agent's settings across configuration tiers — this agent's own say, then the project's, the repo's, the account's — where the nearest tier that set something wins and a tier that said nothing does not participate. +## User Stories + +- The user sets an option on one agent and it wins over the same option set for the project, the repo, or the account — an explicit off included. +- The user who configures nothing still gets finished work handed back: the defaults open a draft pull request. +- The user reads, for each setting the agent ran with, which tier decided it. + ## Flows - An explicit off in a nearer tier beats an on in a farther one. diff --git a/packages/the-framework/src/config.SPEC.md b/packages/the-framework/src/config.SPEC.md index b1800d0c9..f9cc3c77b 100644 --- a/packages/the-framework/src/config.SPEC.md +++ b/packages/the-framework/src/config.SPEC.md @@ -1,5 +1,11 @@ The per-repo defaults every agent starts from, persisted in a small YAML file (`the-framework.yml`) so a project's way of being built — its prompt switches, and what happens when an agent finishes — travels with the code instead of being retyped each time. +## User Stories + +- The user commits `the-framework.yml` and every agent in the repo starts from its defaults — nothing retyped per agent. +- The user overrides the file for a single agent with an explicit flag. +- The user who mistypes a setting gets a warning naming the problem — never a failed agent, and never a silently applied guess. + ## Flows - The file is read from the workspace root; a missing one yields nothing, and a malformed one is a warning and nothing. diff --git a/packages/the-framework/src/control.SPEC.md b/packages/the-framework/src/control.SPEC.md index 6de66dde9..0364b402f 100644 --- a/packages/the-framework/src/control.SPEC.md +++ b/packages/the-framework/src/control.SPEC.md @@ -1,10 +1,18 @@ The steering channel from the dashboard to a live agent — the reverse of the event stream: the daemon appends an instruction to a file in the workspace and the agent tails it, with no direct connection between the two. +## User Stories + +- The user stops a live agent from the dashboard. +- The user answers an agent's parked question and the agent resumes with the pick. +- The user chats with a live agent. +- The user moves how far the agent will publish itself when it finishes, while it is still running. +- The user clicks Merge on a live agent, and that pre-commitment outranks the agent's own ready signal. + ## Flows -- The instructions: stop the agent, answer a parked gate, send a live chat message, move the end-of-work handoff, and a human's Merge — a pre-commitment that outranks the agent's own ready signal. +- The instructions: stop the agent, answer a parked gate (a question the agent paused on), send a live chat message, move the end-of-work handoff, and a human's Merge — a pre-commitment that outranks the agent's own ready signal. - The file is emptied when an agent starts, so a previous agent's answers can never fire into this one. -- The handoff instruction is one rung of the publish ladder, not a set of stage flags: a surface offering the stages as separate boxes resolves them on its own side. +- The handoff instruction is one rung of the publish ladder (keep it local / push / open a pull request / merge), not a set of stage flags: a surface offering the stages as separate boxes resolves them on its own side. - Every line is shape-checked and a bad one is skipped. ## Rationales diff --git a/packages/the-framework/src/daemon-runtime.SPEC.md b/packages/the-framework/src/daemon-runtime.SPEC.md index 9f92cd641..355034a76 100644 --- a/packages/the-framework/src/daemon-runtime.SPEC.md +++ b/packages/the-framework/src/daemon-runtime.SPEC.md @@ -1,5 +1,13 @@ What the daemon does for a project: starting agents in isolated checkouts, retiring them when they end, and keeping every one of them recoverable. +## User Stories + +- The user starts any number of agents and their own checkout is never touched: each agent works in its own worktree, on its own branch. +- The user whose chosen driver cannot run is refused up front, before a branch or worktree is spent. +- The user Ctrl-C's the daemon and later continues a stopped agent in the same checkout, same conversation. +- The user can recreate any reclaimed agent's work from its branch: a checkout goes only once the work is on the remote. +- The user sees a start that died named with its cause — never an agent stuck "waiting to start". + ## Flows - Each agent gets its own worktree and branch, so concurrent agents never touch each other or the user's checkout; a project that is not a Git repo falls back to its main checkout, one agent at a time. A repo whose worktree cannot be created fails the start rather than borrowing the user's working tree. @@ -7,7 +15,7 @@ What the daemon does for a project: starting agents in isolated checkouts, retir - The agents the daemon is still responsible for — spawning, running, or mid-retirement — are named for the background sweep, so it never reclaims a checkout out from under a teardown. - A finished agent's history is archived onto the data branch — committed and pushed the moment the session settles — and its checkout is reclaimed once the work reaches the remote: the one retention rule, applied whatever the agent did. A push that cannot land keeps the checkout, and the background sweep retries it later. - An agent killed by a transient connection error is continued automatically (at most twice), and a child that died before booting is marked failed with the cause surfaced — never left "waiting to start" forever. -- On shutdown, live agents are stopped rather than orphaned; each keeps its worktree and branch, so the dashboard can continue the same conversation in the same checkout when asked. A start that lands while the daemon is shutting down is refused rather than spawned into the gap between the stop pass and the server closing, where nothing would ever stop it — and the refusal takes back the fresh worktree and branch it had already allocated, which no agent ever owned. An agent can also be forwarded to a connected device, its events relayed back. +- On shutdown, live agents are stopped rather than orphaned; each keeps its worktree and branch, so the dashboard can continue the same conversation in the same checkout when asked. A start that lands while the daemon is shutting down is refused rather than spawned into the gap between the stop pass and the server closing, where nothing would ever stop it. The refusal takes back the fresh worktree and branch it had already allocated, which no agent ever owned. An agent can also be forwarded to a connected device, its events relayed back. - Stopping resolves when the daemon has let go of the repo, not when the processes die: a child's exit event lands after its pid disappears, and the teardown that event starts — archive the agent, commit its work, keep or remove its checkout — runs well past that. A teardown that wedges costs the shutdown its grace period, not the exit. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/daemon-services.SPEC.md b/packages/the-framework/src/daemon-services.SPEC.md index 3d0dbb4be..80b9ee5df 100644 --- a/packages/the-framework/src/daemon-services.SPEC.md +++ b/packages/the-framework/src/daemon-services.SPEC.md @@ -1,11 +1,19 @@ Everything the daemon runs in the background beside serving the dashboard: Discord notifications, automatic project management, CI watching, data-branch syncing, and disk reclamation. +## User Stories + +- The user toggles a background service on the dashboard and it takes effect without restarting the daemon. +- The user pastes a Discord webhook into the dashboard and notifications start immediately. +- The user walks away and idle quota is spent on the roadmap: unattended agents drain the confirmed queue, CI-green pull requests are merged, and red ones get a fix agent. +- The user reads on this machine what other machines and cloud sessions pushed, without waiting. +- The user sees a project flagged when its shared data cannot reach origin, and the flag clears with the first sync that converges. + ## Flows - One clock runs every background job, each declaring how many ticks it wants between turns rather than owning an interval. - Every service re-reads its preference on each tick, so a dashboard toggle takes effect without restarting the daemon. - An agent the daemon starts resolves its options from the same two tiers the launcher uses — your settings, then the repo's committed file — so one nobody asked for and one someone clicked differ only in who asked. -- Auto PM spends idle quota on the roadmap: it fans out up to the configured number of unattended agents, each pinned to one queue entry, and retires an entry on the data branch once its agent's ending reports the work published; the daemon, never the agent, writes queue check-offs and ticket locks. +- Auto PM spends idle quota on the roadmap: it fans out up to the configured number of unattended agents, each pinned to one queue entry. An entry is retired on the data branch (the dedicated branch the framework's shared records live on) once its agent's ending reports the work published — and it is the daemon, never the agent, that writes queue check-offs and ticket locks. - The CI watch merges a watched PR once its checks pass, and puts a fix agent on one whose checks fail. - An hourly sweep deletes the dead refs Claude-web hand-offs leave on origin, once they are old enough and provably hold no work. - Settled web runs are matched to the `claude/*` branch that grew out of their hand-off, and the branch and its PR are adopted onto the run's record — with the armed draft PR opened when the session never opened one. diff --git a/packages/the-framework/src/daemon.SPEC.md b/packages/the-framework/src/daemon.SPEC.md index 71aabd3f8..8239370ef 100644 --- a/packages/the-framework/src/daemon.SPEC.md +++ b/packages/the-framework/src/daemon.SPEC.md @@ -1,5 +1,11 @@ The process behind the dashboard: it serves the UI, spawns agents, and runs the background services. +## User Stories + +- The user runs one foreground command and gets the dashboard; Ctrl-C closes it and every agent with it, and nothing burns quota afterwards. +- The user reaches the dashboard on localhost out of the box; putting it on the network takes a generated shared token. +- The user adds each repo through the dashboard — the one onboarding path. + ## Flows - It runs in the foreground and only in the foreground. Ctrl-C closes the dashboard and every agent it is running, so there is no liveness record, no machine-global state file, and no second process to find, reuse or stop. diff --git a/packages/the-framework/src/data-branch.SPEC.md b/packages/the-framework/src/data-branch.SPEC.md index 63657f871..4976442b3 100644 --- a/packages/the-framework/src/data-branch.SPEC.md +++ b/packages/the-framework/src/data-branch.SPEC.md @@ -1,9 +1,16 @@ All the data The Framework writes — the tickets, the task queue, the session archives — lives on one dedicated branch, `tf-data`, so the code history stays 100% code and the data can be pushed and pulled eagerly without ever touching anyone's working tree. +## User Stories + +- The user's code history stays 100% code: the tickets, the queue, and the session archives live on their own branch, and no framework write ever touches a working tree. +- The user finds the roadmap one `ls` away — `tickets` at the repo root — and it never shows up as a change to commit. +- The user reads the same data on every machine and in every cloud session. +- The user is told when the data branch cannot converge with origin: an error to fix, not a silent mode. + ## Flows - The branch is checked out at `.the-framework/branches/tf-data`, and a `tickets` symlink at the repo root points into it, so the roadmap stays one `ls` away. The symlink is hidden from git through a repo-level exclude, so no sweeping commit ever drags it onto a code branch. -- The daemon is the only local writer: every write is one serialized cycle — sync with origin, apply the change, commit, push — so a lost race re-reads the fresher state and re-applies the intent instead of clobbering what someone else landed. +- The daemon is the only local writer: every write is one serialized cycle — sync with origin, apply the change, commit, push. A lost race re-reads the fresher state and re-applies the intent, instead of clobbering what someone else landed. - Every machine (and every cloud session) converges on the same data by pulling the branch eagerly; a write that cannot reach the network stays committed locally and rides out on the next cycle. The eager pull says when it could not converge — origin rejected the push, or the repository has no remote to converge with — because a data branch nobody else can reach is an error for the user to fix, not a mode. - A project with the branch already on origin adopts it; a project without one births it with an empty history of its own, unrelated to the code's. diff --git a/packages/the-framework/src/discord-credentials-store.SPEC.md b/packages/the-framework/src/discord-credentials-store.SPEC.md index b2bbcab12..dc5ce8ae7 100644 --- a/packages/the-framework/src/discord-credentials-store.SPEC.md +++ b/packages/the-framework/src/discord-credentials-store.SPEC.md @@ -1,5 +1,10 @@ Reads and writes the two Discord credentials in the user's registry file, and tells the running daemon when they change so a pasted token works without a restart. +## User Stories + +- The user pastes Discord credentials into the dashboard and they take effect without restarting the daemon. +- The user who tries to edit an environment-owned credential is told no, and told why. + ## Flows - A credential set in the daemon's environment cannot be edited here: the save is refused with an explanation instead. diff --git a/packages/the-framework/src/discord-credentials.SPEC.md b/packages/the-framework/src/discord-credentials.SPEC.md index a9bbd23ad..9c8bc476f 100644 --- a/packages/the-framework/src/discord-credentials.SPEC.md +++ b/packages/the-framework/src/discord-credentials.SPEC.md @@ -1,5 +1,11 @@ The rules for where the daemon's Discord notification webhook comes from: the environment first, then a value saved from the dashboard. +## User Stories + +- The user turns on Discord notifications from the dashboard alone — or a deployment sets the webhook in the daemon's environment. +- The user sees which credential exists and where it came from, and is never offered an edit that would not take effect. +- The user can never read a stored credential back out of the dashboard. + ## Flows - Credentials can be set in the daemon's environment or saved from the dashboard; a dashboard save is picked up live, without a restart. diff --git a/packages/the-framework/src/driver-cli.SPEC.md b/packages/the-framework/src/driver-cli.SPEC.md index fd81cb2c0..1d81104d7 100644 --- a/packages/the-framework/src/driver-cli.SPEC.md +++ b/packages/the-framework/src/driver-cli.SPEC.md @@ -1,5 +1,10 @@ What the framework knows about each driver it can run — a whole coding-agent CLI the user already pays for, driven on their own subscription with no API key — and the one place an agent turns the picked driver into a live implementation. +## User Stories + +- The user picks which coding-agent CLI does the work, and it runs on the subscription they already pay for — no API key. +- The user with a dead setup — the driver not installed, or logged out — is told before any quota is spent, with the fix named. + ## Flows - Each driver declares its binary, an install hint, how to ask it "am I logged in?", and the one command that fixes a no — so a dead setup is caught before any quota is spent. diff --git a/packages/the-framework/src/events.SPEC.md b/packages/the-framework/src/events.SPEC.md index eae1c0cb3..8555b5c30 100644 --- a/packages/the-framework/src/events.SPEC.md +++ b/packages/the-framework/src/events.SPEC.md @@ -1,9 +1,15 @@ The single event stream an agent narrates itself over: one timeline uniting the framework's own steps, the driver's progress, and the moments that need a human. +## User Stories + +- The user watches one timeline per agent — the framework's steps, the driver's progress, the questions that need a human — and it reads the same in the terminal, the dashboard, and chat. +- The user opens a dashboard tab at any point and nothing is missing, because whatever a later reader must know travels as an event. +- The user always finds a reason on the record when something that was switched on did not happen. + ## Flows - The framework owns the stream rather than exposing the driver's transport, so every surface — terminal, dashboard, chat — renders the same story. -- Events are the agent's durable record: anything a dashboard tab opened later must know (the ticket being implemented, the branch, the pull request opened for the work, the hand-off anchor a cloud run's branch is later recognized by, what the end-of-work handoff is armed to do) travels as an event, because only events reach its stored history. +- Events are the agent's durable record: only events reach its stored history, so anything a dashboard tab opened later must know travels as an event — the ticket being implemented, the branch, the pull request opened for the work, the marker commit (hand-off anchor) a cloud run's branch is later recognized by, and what the end-of-work handoff is armed to do. - Interactive gates are events too: a choice pauses the agent until a pick is posted back, and both the question and who answered it are on the record. - Every skipped or withheld outcome carries its reason, so "it was on and nothing happened" always has an answer in the log. diff --git a/packages/the-framework/src/fake-script.SPEC.md b/packages/the-framework/src/fake-script.SPEC.md index 47c287ada..00ad9382e 100644 --- a/packages/the-framework/src/fake-script.SPEC.md +++ b/packages/the-framework/src/fake-script.SPEC.md @@ -1,5 +1,9 @@ The deterministic offline demo: a scripted fake agent builds a small orders app so the whole flow can be shown with no agent CLI and no model. +## User Stories + +- The user demos the whole flow with no agent CLI and no model: a scripted agent builds the same small orders app every time. + ## Flows - Optional variants make the scripted build pause on each kind of interactive gate (single choice, checklist, plan approval), so the ask-and-resume flow is demonstrable offline too. diff --git a/packages/the-framework/src/framework-gitignore.SPEC.md b/packages/the-framework/src/framework-gitignore.SPEC.md index e55c572cf..f444bcf94 100644 --- a/packages/the-framework/src/framework-gitignore.SPEC.md +++ b/packages/the-framework/src/framework-gitignore.SPEC.md @@ -2,7 +2,7 @@ The `.the-framework/.gitignore`: everything under a project's framework director ## Flows -- One file with one content, written whole at install: ignore it all, keep only the ignore file itself and the layout marker. +- One file with one content, written whole at install: ignore it all, keep only the ignore file itself and the layout marker (the committed note of which bookkeeping layout the repo is on) — so the framework's state never shows up as uncommitted changes in anyone's checkout. ## Rationales diff --git a/packages/the-framework/src/handoff-level.SPEC.md b/packages/the-framework/src/handoff-level.SPEC.md index b0b2d674f..5b496b473 100644 --- a/packages/the-framework/src/handoff-level.SPEC.md +++ b/packages/the-framework/src/handoff-level.SPEC.md @@ -1,5 +1,10 @@ How far a finished agent publishes itself — one ordinal covering keep it local, push the branch, open a pull request, merge it. +## User Stories + +- The user picks how far a finished agent publishes itself — keep it local, push the branch, open a pull request, or merge — as one choice. +- The user who picks nothing gets a pull request opened for the work, so it never sits on a local branch nobody is told about; merging always has to be asked for. + ## Flows - One ladder, not three switches. The stages are strictly nested — a pull request needs a pushed branch, a merge needs a pull request — so a rung includes every rung below it, and the impossible combinations are not representable. diff --git a/packages/the-framework/src/install.SPEC.md b/packages/the-framework/src/install.SPEC.md index 1ef2ed661..a44b3d19b 100644 --- a/packages/the-framework/src/install.SPEC.md +++ b/packages/the-framework/src/install.SPEC.md @@ -1,9 +1,16 @@ Activates a repo for the framework: it becomes a Git repo if it is not one yet, gets the framework's marker directory and its ignore rules, and the activation itself is committed. +## User Stories + +- The user activates a repo and it is ready for agents: made a Git repo if it was not one, given the framework's directory and ignore rules, the activation committed. +- The user activates a dirty checkout without losing anything: pre-existing changes land in their own commit, never mixed into the install's. +- The user adds a whole folder of projects at once: each immediate child that is its own Git repo is found. +- The user activates the same repo twice and the second activation is a harmless no-op. + ## Flows - Pre-existing uncommitted changes are committed first, so the install commit is clean and none of the user's work is mixed into it. -- The ignore rules keep every bit of agent state out of Git on the code branches (the durable records live on the data branch); the quality presets are materialized so their references resolve, but regenerate per install rather than being committed. +- The ignore rules keep every bit of agent state out of Git on the code branches (the durable records live on the framework's dedicated data branch); the presets the framework ships are materialized so their references resolve, but regenerate per install rather than being committed. - The ignore file is also the activation marker: it is the one file install always writes, so a repo that has it is already activated. - Install also records the layout marker — the committed note of where this build keeps its bookkeeping, the data branch's name included — so a differently-laid-out build later refuses to run in the repo instead of committing files under the wrong names. - Activating an already-activated repo is a harmless no-op, and any failure comes back as an error value, never a throw. diff --git a/packages/the-framework/src/jsonl-tail.SPEC.md b/packages/the-framework/src/jsonl-tail.SPEC.md index 267d03e73..e9590b9ba 100644 --- a/packages/the-framework/src/jsonl-tail.SPEC.md +++ b/packages/the-framework/src/jsonl-tail.SPEC.md @@ -4,7 +4,7 @@ Follows an append-only line-per-record log as it grows — the seam through whic - Reads only what was appended since last time, holds back a half-written line until it completes, and starts over when a fresh agent truncates the log. - A tail can follow its log to a new home when the file is moved with content intact, without replaying what it already delivered. -- Watches the directory for low latency with a polling backstop for reliability; nothing that goes wrong in either may crash the process, and a tail can opt out of keeping the process alive so steering never holds a finished agent open. +- Watches the directory for low latency, with a polling backstop for reliability; nothing that goes wrong in either may crash the process. A tail can opt out of keeping the process alive, so steering never holds a finished agent open. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/layout.SPEC.md b/packages/the-framework/src/layout.SPEC.md index 2c95240c1..760242e5c 100644 --- a/packages/the-framework/src/layout.SPEC.md +++ b/packages/the-framework/src/layout.SPEC.md @@ -1,5 +1,10 @@ The layout gate: a framework build refuses to run in a repo that records a different bookkeeping layout, instead of committing files under names the repo no longer uses. +## User Stories + +- The user whose installed build and repo disagree on the bookkeeping layout is refused at session start — no degraded mode, no wrong-layout commits rejected hours later. +- The user reads, in the refusal, both layouts and the fix for each direction. + ## Flows - Every activated repo carries a small committed marker naming the layout its bookkeeping is on (the data branch's name, and where archives, tickets, and the queue live). From 01de50ef728dd12a33c6cd84a98228d0f234d503 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 08:57:06 +0000 Subject: [PATCH 03/16] Style pass: re-voice the driver specs to the user-story axis User Stories on the drivers the user picks (Claude Code, Codex, Actions); flows anchored on what the dashboard shows and what the user's subscription pays; the Actions spec's shell-safety and auth claims sharpened to the code-accurate mechanisms; coined terms ("seam", "barrel") replaced or glossed; dense bullets split. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/src/driver/SPEC.md | 14 +++++++++++--- .../the-framework/src/driver/actions-zip.SPEC.md | 2 +- .../the-framework/src/driver/actions.SPEC.md | 16 +++++++++++----- .../src/driver/child-registry.SPEC.md | 4 ++-- .../src/driver/claude-code-quota.SPEC.md | 12 ++++++++---- .../the-framework/src/driver/claude-code.SPEC.md | 12 +++++++++--- .../the-framework/src/driver/cli-session.SPEC.md | 4 ++-- packages/the-framework/src/driver/codex.SPEC.md | 6 +++++- packages/the-framework/src/driver/index.SPEC.md | 4 ++-- packages/the-framework/src/driver/types.SPEC.md | 6 +++--- 10 files changed, 54 insertions(+), 26 deletions(-) diff --git a/packages/the-framework/src/driver/SPEC.md b/packages/the-framework/src/driver/SPEC.md index 687ccc8d1..e50b9a674 100644 --- a/packages/the-framework/src/driver/SPEC.md +++ b/packages/the-framework/src/driver/SPEC.md @@ -1,14 +1,22 @@ The driver seam: the one abstraction a coding-agent CLI is wrapped behind, so the whole product works the same whether the work happens in a local Claude Code or Codex process, a Claude cloud session, a GitHub Actions job, or a deterministic fake. +## User Stories + +- The user picks which coding-agent CLI does the work — Claude Code or Codex — and nothing built on top changes with the pick. +- The user's work runs on their own subscription — Claude or ChatGPT — never on an API key held by the product. +- The user sends the same task to their own machine, a GitHub Actions runner, or a Claude Code cloud session. +- The user watches the agent work — its words, its tool calls as named actions — and sees what each turn spent. +- The user sees where the account's subscription quota stands on the dashboard. + ## Flows - **The contract.** A driver can start a session, prompt it turn by turn, read the resulting code, report the account's quota, and dispose — that is the entire contract. Tool calls surface only as named actions for the watching human; their arguments are never seen and never branched on. - **Black-box turns.** Each turn runs the CLI's own loop to completion as a black box. A turn whose process exits abnormally fails, even if it streamed plausible text first. -- **The resume handle.** The session id is captured from the first thing the CLI says, not from the end of the turn. A resume that fails because the CLI forgot the conversation silently reruns fresh, with a notice, rather than losing the message the user already sent. +- **The resume handle.** The session id is captured from the first thing the CLI says, not from the end of the turn. When the user's message tries to resume a conversation the CLI has forgotten, the turn reruns fresh with a notice — the message is never lost, and no failed turn is shown for one that recovered. - **Process hygiene.** Every CLI invocation runs as its own process tree, and stopping an agent (or the framework dying) kills the whole tree — no stray processes outlive the agent that spawned them. - **Local CLIs.** Claude Code runs with edits auto-accepted by default; skipping its permission system entirely is an explicit opt-in. Codex runs inside its own workspace sandbox and the bypass is never passed. Extra capabilities (e.g. the real browser) are merged alongside the user's own tools, never replacing them. -- **Hands-off locations.** A cloud session is handed the task exactly once per agent — so one agent can never fan out into several cloud sessions racing on one repo — and it ends with the link: there is nothing to read back. Whether later phases run is a fact about *where* the turn executed, so it is settled by the location rather than declared by the driver. A GitHub Actions workflow run is dispatched, polled, and read back from the transcript the workflow uploads; continuity between turns is the branch the previous turn pushed plus the carried session id, and it requires a real user's token — a bot-triggered dispatch is refused. -- **Quota and cost.** Drivers that can, report the account's quota window: read via the CLI's own usage command, and for free between turns from the telemetry the stream already carries. A transient failure (network, timeout, reworded readout) leaves the last good reading in force; "the CLI isn't installed / has no subscription" invalidates it. A driver that cannot price turns reports cost as unknown — never zero — so an unpriced agent never reads as free. +- **Hands-off locations.** A cloud session is handed the task exactly once per agent — so one agent can never fan out into several cloud sessions racing on one repo — and it ends with the link: there is nothing to read back. Whether later phases run is a fact about *where* the turn executed, so it is settled by the location rather than declared by the driver. A GitHub Actions workflow run is dispatched, polled, and read back from the transcript the workflow uploads. Its continuity between turns is the branch the previous turn pushed plus the carried session id, and its dispatch requires a real user's token — a bot-triggered run is refused. +- **Quota and cost.** Drivers that can, report the account's quota window: read via the CLI's own usage command — the reading that fills the dashboard's usage panel — and for free between turns from the telemetry the stream already carries. A transient failure (network, timeout, reworded readout) leaves the last good reading in force; "the CLI isn't installed / has no subscription" invalidates it. A driver that cannot price turns reports cost as unknown — never zero — so an unpriced agent never reads as free. - **The fake.** A scripted, offline driver that exercises every path deterministically — the product's whole lifecycle is testable without spending a token. ## Rationales diff --git a/packages/the-framework/src/driver/actions-zip.SPEC.md b/packages/the-framework/src/driver/actions-zip.SPEC.md index 67022f8ec..38283c925 100644 --- a/packages/the-framework/src/driver/actions-zip.SPEC.md +++ b/packages/the-framework/src/driver/actions-zip.SPEC.md @@ -1,4 +1,4 @@ -Reads the archive a GitHub Actions run uploads — the only channel the transcript can come back through — and refuses anything it does not fully understand. +Reads the archive a GitHub Actions run uploads — the only channel the agent's transcript can come back through — and refuses any archive it cannot read in full rather than returning part of one. ## Rationales diff --git a/packages/the-framework/src/driver/actions.SPEC.md b/packages/the-framework/src/driver/actions.SPEC.md index 7dcf2979e..3c7f054da 100644 --- a/packages/the-framework/src/driver/actions.SPEC.md +++ b/packages/the-framework/src/driver/actions.SPEC.md @@ -1,11 +1,17 @@ -A driver that runs the agent on GitHub Actions instead of this machine: dispatch a workflow, wait for it, read back the transcript the agent uploads. +A driver that runs the agent on GitHub Actions instead of the user's machine: dispatch a workflow, wait for it, read back the transcript the agent uploads. + +## User Stories + +- The user sends an agent to a fresh GitHub Actions runner instead of their machine and still reads the same transcript on the dashboard. +- The user's own Claude subscription pays for the runs: the repo holds a token the user minted, never an API key of the product's. ## Flows -- Same contract, different tempo: every prompt is a fresh runner, turns take minutes, and progress replays in one burst at the end instead of trickling live. -- Continuity across turns is the branch the previous turn pushed — the next one starts from it — plus the carried session id, so a multi-pass agent keeps building on its own work; produced code is read off that branch, because the runner is gone by the time we ask. -- Auth is the same bring-your-own-subscription posture as everywhere else: a token held by the repo, belonging to a real user — bot-triggered agents are refused. -- Each dispatch carries a unique correlation tag so a restarted framework can never mistake a stale or foreign run for its own, and every value forwarded to the runner is checked so nothing can act as shell syntax. +- The same driver contract at a different tempo: every prompt is a fresh runner, turns take minutes, and the agent's progress replays in one burst at the end instead of trickling live. +- Continuity across turns is the branch the previous turn pushed plus the carried session id: the next turn starts from that branch, so a multi-pass agent keeps building on its own work. The code the agent produced is read off that branch too, because the runner is gone by the time anyone asks. +- The token that dispatches a run must belong to a real user — a bot-triggered agent is refused. +- Each dispatch carries a unique correlation tag, so a restarted framework can never mistake a stale or foreign run for its own. +- Nothing the user typed can reach the runner's shell as syntax: the prompt travels as workflow input data, and the two values that do land in that shell — the model name and the resume session id — are refused unless they are plain ids. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/driver/child-registry.SPEC.md b/packages/the-framework/src/driver/child-registry.SPEC.md index 105e1056a..b136578fe 100644 --- a/packages/the-framework/src/driver/child-registry.SPEC.md +++ b/packages/the-framework/src/driver/child-registry.SPEC.md @@ -1,8 +1,8 @@ -Makes sure no driver process ever outlives the agent that spawned it: each spawned child leads its own process tree, and stopping an agent — or the framework itself dying — reaps the whole tree instead of orphaning it. +No driver process ever outlives the agent that spawned it: each spawned child leads its own process tree, and stopping an agent — or the framework itself dying — takes the whole tree down instead of orphaning it. ## Flows -- Every live tree is tracked so even a hard crash of the framework still takes them all down on the way out. +- Every live tree is tracked so even a hard crash of the framework still takes them all down on the way out — the user's machine is never left running stray agent processes. ## Rationales diff --git a/packages/the-framework/src/driver/claude-code-quota.SPEC.md b/packages/the-framework/src/driver/claude-code-quota.SPEC.md index 55d14b367..8c6ca1a2a 100644 --- a/packages/the-framework/src/driver/claude-code-quota.SPEC.md +++ b/packages/the-framework/src/driver/claude-code-quota.SPEC.md @@ -1,11 +1,15 @@ -Asks Claude Code where the account's subscription quota stands, by running the agent's own usage readout and parsing the prose it prints. +Asks Claude Code where the account's subscription quota stands, by running the CLI's own usage command and parsing the prose it prints. + +## User Stories + +- The user sees how much of their Claude subscription each quota window has consumed, and looking it up spends none of it. ## Flows -- The read costs nothing — the agent answers locally without prompting a model — and it runs with the agent's own credentials, so the product never touches the user's token. +- The read costs nothing — the CLI answers locally without prompting a model — and it runs with the CLI's own credentials, so the product never touches the user's token. - The readout is prose, so a reworded readout is a real failure mode: an unreadable answer reports "unrecognized", never an empty reading. -- An account with no subscription quota (API-key auth) is told apart from a readout we failed to read: one means no quota exists, the other means try again — and an account burning overage still reports its quota. -- A missing agent, a refused fetch, and a hung read each get their own reason, so callers know whether to keep the last good reading. +- An account with no subscription quota (API-key auth) is told apart from a readout that failed to parse: one means no quota exists, the other means try again — and an account burning overage still reports its quota. +- A missing CLI, a refused fetch, and a hung read each get their own reason, so the dashboard knows whether to keep showing the last good reading. ## Rationales diff --git a/packages/the-framework/src/driver/claude-code.SPEC.md b/packages/the-framework/src/driver/claude-code.SPEC.md index 2c42240d6..32ed09126 100644 --- a/packages/the-framework/src/driver/claude-code.SPEC.md +++ b/packages/the-framework/src/driver/claude-code.SPEC.md @@ -1,11 +1,17 @@ The first real driver: wraps the Claude Code CLI so the product runs work on the user's own Claude subscription, one fresh non-interactive invocation per prompt. +## User Stories + +- The user picks Claude Code and the work runs on their own Claude subscription. +- The user chats with a live agent — or reopens a finished one — and every message continues the same conversation. +- The user hands the agent extra capabilities, like a real browser to drive, without giving up any of their own tools. + ## Flows - File edits are auto-accepted by default so agents never stall on a permission prompt; skipping the CLI's permission system entirely is a separate, explicit opt-in meant for sandboxes. -- A chat turn can resume the agent's previous conversation so the message lands with full context; when that conversation no longer exists, the turn reruns fresh with a notice rather than losing the message the user already typed — and without showing a failed turn for one that recovered. -- Extra capabilities (like the real browser) are offered as additional tool servers that merge with the user's own, never replacing them. -- The CLI's stream is mined for free telemetry: its session id (announced at the start of the turn), assistant text, tool names, per-turn token/cost accounting, and the account's quota standing. +- The user's chat message resumes the agent's previous conversation, so it lands with full context. When the CLI has forgotten that conversation, the turn reruns fresh with a notice rather than losing the message the user already typed — and no failed turn is shown for one that recovered. +- Extra capabilities are offered as additional tool servers that merge with the user's own, never replacing them. +- The CLI's stream is mined for free telemetry — the session id (announced at the start of the turn), assistant text, tool names, per-turn token and cost accounting, and the account's quota standing — which is what the dashboard's transcript and spend readout draw from, with no extra calls. ## Rationales diff --git a/packages/the-framework/src/driver/cli-session.SPEC.md b/packages/the-framework/src/driver/cli-session.SPEC.md index 50fd49a46..94452665b 100644 --- a/packages/the-framework/src/driver/cli-session.SPEC.md +++ b/packages/the-framework/src/driver/cli-session.SPEC.md @@ -2,9 +2,9 @@ The one way any driver CLI is run: spawn it over the workspace, hand it the prom ## Flows -- A turn fails on an abnormal exit even when the agent streamed plausible text first; what the agent said on the way down becomes the error detail. +- A turn fails on an abnormal exit even when the agent streamed plausible text first; what the agent said on the way down — its error output, or failing that its partial answer — becomes the error detail. - Stopping a turn kills the agent's entire process tree: asked nicely first, forced after a grace period. -- The prompt travels over the agent's input stream, so its length is unlimited and none of it can be misread as a command; an agent that dies before reading it fails the turn cleanly instead of crashing the product. +- The prompt travels over the agent's input stream, so its length is unlimited and none of it can be misread as a command. An agent that dies before reading the prompt fails the turn cleanly instead of crashing the product. - Only the parser knows which CLI is on the other end, so a second driver gets all of this for free. ## Rationales diff --git a/packages/the-framework/src/driver/codex.SPEC.md b/packages/the-framework/src/driver/codex.SPEC.md index 268049779..cb2942631 100644 --- a/packages/the-framework/src/driver/codex.SPEC.md +++ b/packages/the-framework/src/driver/codex.SPEC.md @@ -1,10 +1,14 @@ The second real driver: wraps the Codex CLI so the product runs the same way on the user's own ChatGPT subscription. +## User Stories + +- The user picks Codex and the same product runs on their own ChatGPT subscription instead. + ## Flows - Codex works inside its own workspace sandbox — it can edit the workspace it was pointed at and nothing else — and the sandbox-bypass option is never passed. - Codex takes no separate system prompt, so role framing is prepended to the prompt; the same words reach the CLI. -- It reports tokens but never a price, so turns carry the counts with the cost left unknown — never zero — which means the budget cap simply cannot fire here; cached input is split out of the inclusive total and reasoning tokens are not double-counted, so accounting means the same thing across drivers. +- Codex reports tokens but never a price, so turns carry the counts with the cost left unknown — never zero — and the budget cap simply cannot fire here. Cached input is split out of the inclusive total and reasoning tokens are not double-counted, so a token count means the same thing whichever CLI the user picked. - No quota read: a driver that can't report one simply doesn't. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/driver/index.SPEC.md b/packages/the-framework/src/driver/index.SPEC.md index 6a478bb84..e7ac42a04 100644 --- a/packages/the-framework/src/driver/index.SPEC.md +++ b/packages/the-framework/src/driver/index.SPEC.md @@ -1,8 +1,8 @@ -The driver seam's public doorway: one import for the contract, every driver (Claude Code, Codex, GitHub Actions, cloud, fake), and the quota reader — what a driver needs internally does not pass through here. +The drivers' public doorway: one import for the driver contract, every driver (Claude Code, Codex, GitHub Actions, cloud, fake), and the quota reader — what a driver needs internally does not pass through here. ## Rationales -- The root barrel re-exports this doorway wholesale, so anything listed is published, and an accidental export is a one-way door once released. +- The package's root export re-exports this doorway wholesale, so anything listed here is published, and an accidental export is a one-way door once released. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/driver/types.SPEC.md b/packages/the-framework/src/driver/types.SPEC.md index 2722b6562..02ff1ccb1 100644 --- a/packages/the-framework/src/driver/types.SPEC.md +++ b/packages/the-framework/src/driver/types.SPEC.md @@ -2,10 +2,10 @@ The contract every wrapped coding agent must fit: start a session in a workspace ## Flows -- The seam is deliberately the code and the outcome, never the agent's tool calls: tools surface only as named actions for the watching human, and control flow never branches on them. -- Each prompt is the fresh-context unit; personas are framing text carried on the session, not separate agents. +- The contract is deliberately the code and the outcome, never the agent's tool calls: tools surface only as named actions for the watching human, and control flow never branches on them. +- Each prompt is the fresh-context unit; a persona — a role the agent is asked to play — is framing text carried on the session, not a separate agent. - A turn reports the tokens it spent, but a price only when the agent priced it — an unknown cost is omitted, never zero, so "free" and "unknown" can't be confused. -- A quota reading is available-with-windows or unavailable-with-a-reason, and the reasons split "this attempt failed" (keep showing the last good reading) from "this setup has no quota" (drop it). +- A quota reading is available-with-windows or unavailable-with-a-reason, and the reasons split "this attempt failed" — the dashboard keeps showing the last good reading — from "this setup has no quota", which drops it. - A driver answers only "which CLI do I spawn". Whether an agent ends at its first prompt is a fact about where that prompt executed, and lives with the location instead. ## Before modifying/creating SPEC.md files From 209558c31d254a2827fac16cc415273e1de6933f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 08:57:21 +0000 Subject: [PATCH 04/16] Style pass: re-voice store, e2e, prompts, and scripts specs to the user-story axis User Stories on the persistence guarantees the user relies on (history that survives git clean, work that survives removal, one-row continuations) and on prompt transparency; snapshot mechanics flipped observable-first; coined terms glossed; the agent-store test spec's one-sentence monster split into line 1 plus five bullets. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/prompts/SPEC.md | 14 +++++++++---- packages/the-framework/scripts/SPEC.md | 4 ++-- packages/the-framework/src/e2e/SPEC.md | 2 +- .../the-framework/src/e2e/harness.SPEC.md | 6 +++--- packages/the-framework/src/store/SPEC.md | 9 +++++++- .../src/store/agent-checkout.SPEC.md | 9 ++++++-- .../src/store/agent-store.SPEC.md | 21 ++++++++++++------- .../src/store/agent-store.test.SPEC.md | 9 ++++++-- .../src/store/worktree-deps.SPEC.md | 7 ++++++- .../the-framework/src/store/worktree.SPEC.md | 15 +++++++++---- 10 files changed, 69 insertions(+), 27 deletions(-) diff --git a/packages/the-framework/prompts/SPEC.md b/packages/the-framework/prompts/SPEC.md index cd23a6081..7be1a6bd0 100644 --- a/packages/the-framework/prompts/SPEC.md +++ b/packages/the-framework/prompts/SPEC.md @@ -1,12 +1,18 @@ Every word the framework says to a coding agent, authored as markdown: the built-in system prompt, the protocols agents answer back through, the preset task prompts, and the repo conventions they are taught. +## User Stories + +- The user reads in the dashboard the exact prompt an agent runs under; these files are that text. +- The user picks a preset in the composer and an unattended agent runs with that file as its task. +- The user steers a running agent through gates — questions, plan approvals, a handed-over browser — which agents raise by following the protocols here. + ## Flows -- The system prompt wraps the user's prompt in a working discipline: analyze it and gate on ambiguity or large scope, keep every read and write under the working directory, name the session and do all work on a branch of that name, offer alternatives wherever the best solution is unclear, and signal ready-for-merge only when nothing is left — without that signal the work is never merged. -- The protocols define the agent's side of the conversation: how to park on a gate (a choice, a multi-select, a document approval, handing the browser to a human at a login wall) — including marking the answers that end the agent rather than resume it, so a rejection is not something it is asked to build on — and how to emit the non-blocking signals (show a document, name the session, ready-for-merge); per-capability protocols adapt it — an agent with a real browser is told when to use it, a hands-off agent is told gates can never be answered, so assume the recommended option and carry on. +- The system prompt wraps the user's prompt in a working discipline: analyze it and gate on ambiguity or large scope, keep every read and write under the working directory, name the session and do all work on a branch of that name, offer alternatives wherever the best solution is unclear, and signal ready-for-merge only when nothing is left. Without that signal the work is never merged. +- The protocols define the agent's side of the conversation. One teaches how to park on a gate — a question that blocks the agent until the user answers: a choice, a multi-select, a document approval, handing the browser to a human at a login wall — and how to mark the answers that end the agent rather than resume it, so a rejection is not something it is asked to build on. Another teaches the non-blocking signals: show a document, name the session, ready-for-merge. Per-capability protocols adapt the rest: an agent with a real browser is told when to use it, and a hands-off agent is told gates can never be answered, so it assumes the recommended option and carries on. - The presets are the one-click task prompts behind the dashboard's buttons: research, the quality reviews (readability, maintainability, security, UX), ticket triage and planning, and draining the queue. -- The format docs teach the repo conventions: tickets as dated proposal files with plan and claim siblings, and the priority-ordered queue file of confirmed work. -- The before-mergeable prompt is the final quality turn: queue follow-up refactor and security passes when the changes warrant them, and fold what the agent learned into the project's knowledge base. +- The format docs teach the repo conventions: tickets as dated proposal files with plan and claim siblings (`.plan.md`, `.lock.md`), and the priority-ordered queue file (`TODO_AGENTS.md`) of confirmed work. +- The before-mergeable prompt is the final quality turn: queue follow-up refactor and security passes when the changes warrant them, and fold what the agent learned into the project's knowledge base (`knowledge-base/*.md`). ## Rationales diff --git a/packages/the-framework/scripts/SPEC.md b/packages/the-framework/scripts/SPEC.md index cf9a7b00c..ef15d51ee 100644 --- a/packages/the-framework/scripts/SPEC.md +++ b/packages/the-framework/scripts/SPEC.md @@ -2,8 +2,8 @@ Build-time helpers that keep the prompting authored as markdown, and make the pu ## Flows -- Every prompt markdown file is compiled into a generated module of plain strings before each build, test, and typecheck; the markdown is the only source of truth. -- Tests run against a throwaway home for the machine's global state, so the developer's live daemon can never leak into the suite and hang it. There are two suites and two runners: Node's test runner over the compiled daemon suite, and the dashboard's own browser-shaped tests. +- Every prompt markdown file is compiled into a generated module of plain strings before each build, test, and typecheck; the markdown is the only source of truth. Daemon and dashboard read the same generated module, so the prompt the user reads in the dashboard is the text the agent runs under. +- Tests run against a throwaway home for the machine's global state, so the developer's live daemon can never leak into the suite and hang it. There are two suites and two runners: Node's test runner over the compiled daemon suite, and the dashboard's own tests in a browser-like environment. ## Rationales diff --git a/packages/the-framework/src/e2e/SPEC.md b/packages/the-framework/src/e2e/SPEC.md index b629ba2ea..29600e442 100644 --- a/packages/the-framework/src/e2e/SPEC.md +++ b/packages/the-framework/src/e2e/SPEC.md @@ -3,7 +3,7 @@ The product's end-to-end stories: each test walks a user journey through the dae ## Flows - Four story files cover the journeys: the agent lifecycle (start, watch live, read the archived row, publish the branch), steering and gates (questions, chat, handoff, stop), projects and settings, and tickets and the work queue. -- Stories observe the product exactly where users do — the dashboard's reads and the live event feed; the one extra window is the recorded child invocation. +- Stories observe the product exactly where users do — the dashboard's reads and the live event feed. The one extra window is the recorded child invocation: a capture of how each agent process was launched. - The harness gives every story a throwaway world with its own global state and a daemon-shaped teardown, so stories are isolated, parallel-safe, and repeatable. ## Rationales diff --git a/packages/the-framework/src/e2e/harness.SPEC.md b/packages/the-framework/src/e2e/harness.SPEC.md index e1d6c45d3..8ac8e014f 100644 --- a/packages/the-framework/src/e2e/harness.SPEC.md +++ b/packages/the-framework/src/e2e/harness.SPEC.md @@ -1,10 +1,10 @@ -Stands up a disposable copy of the product for one story: the daemon's business logic wired exactly as production wires it, real git-repo projects registered through the real add-project call, and agents spawned as real child processes with the fake driver in the driver seat. +Stands up a disposable copy of the product for one story: the daemon's business logic wired exactly as production wires it, real git-repo projects registered through the real add-project call, and agents spawned as real child processes with the fake driver — a scripted stand-in for the coding-agent CLI — in the driver seat. ## Flows -- Stories drive the product only through the same calls the dashboard makes and watch it through the same live event feed, so what a test sees is what a user sees; the pieces the daemon runs as live loops (quota, auto-PM) are stubs a story controls directly. +- Stories drive the product only through the same calls the dashboard makes and watch it through the same live event feed, so what a test sees is what a user sees. The pieces the daemon runs as live loops (quota, auto-PM) are stubs a story controls directly. - "Finished" means two different things — the agent's row says done, and its workspace has actually been retired — and a story can wait for either. -- Each world gets its own throwaway global state, so parallel stories never see each other's projects; a story can also park an agent on a scripted question, and read back exactly how each agent child was invoked. +- Each world gets its own throwaway global state, so parallel stories never see each other's projects. A story can also park an agent on a scripted question, and read back exactly how each agent child was invoked. - Teardown mirrors daemon shutdown: stop the agents, wait out in-flight teardowns, then delete everything. ## Rationales diff --git a/packages/the-framework/src/store/SPEC.md b/packages/the-framework/src/store/SPEC.md index 817e0f657..7a7892054 100644 --- a/packages/the-framework/src/store/SPEC.md +++ b/packages/the-framework/src/store/SPEC.md @@ -1,11 +1,18 @@ Agent persistence and workspaces: the append-only event log every surface is a projection of, and the per-agent git worktrees they work in. +## User Stories + +- The user's own checkout is never touched: each agent works in its own worktree on its own branch. +- The user sees a project's history — every user's finished agents — survive restarts, crashes, and even a `git clean`. +- The user removes an agent's checkout and still finds the work on its branch. +- The user never finds a crashed agent still shown as running. + ## Flows - Persisting is logging: an agent's history is the events it appended, and everything else — status, name, branch, outcomes — is folded from that log into a small snapshot for cheap list reads. A restarted dashboard rehydrates by replaying the log. - An agent that crashed without saying goodbye is healed on a later boot or read: after checking its process is genuinely dead, the missing ending is written on its behalf, so none stays "running" — or keeps asking its last question — forever. - Finished agents are archived into the repo under per-user directories and committed; every user's archive is visible to the whole team, and live state beats the archive when both exist. -- Each agent works in its own worktree on its own branch (named after its id until it picks a session name); teardown commits leftover work first, so the branch outlives the checkout. The parent checkout's installed dependencies are symlinked in instead of reinstalled — instant, no extra disk — and hidden from git so a sweeping commit cannot drag them onto the PR. +- Each agent works in its own worktree on its own branch, named after its id until the agent picks a session name. Teardown commits leftover work first, so the branch outlives the checkout. The parent checkout's installed dependencies are symlinked in instead of reinstalled — instant, no extra disk — and hidden from git so a sweeping commit cannot drag them onto the PR. - Nothing is resumed at boot; what boot does instead is mark as stopped anything a dead process left claiming to be running. ## Rationales diff --git a/packages/the-framework/src/store/agent-checkout.SPEC.md b/packages/the-framework/src/store/agent-checkout.SPEC.md index 6f4146643..039b5a7a2 100644 --- a/packages/the-framework/src/store/agent-checkout.SPEC.md +++ b/packages/the-framework/src/store/agent-checkout.SPEC.md @@ -1,9 +1,14 @@ Resolves which checkout — and which event journal — a session id points at, one shared rule for every surface that addresses a session by id. +## User Stories + +- The user opens an agent — pasted URL, reloaded tab, history row — and every read lands on that agent's own checkout, whatever state the agent is in. +- The user opens an ended agent whose worktree is gone and still reads that agent's own transcript. + ## Flows -- Order: the live agent's own recorded checkout first, then its worktree directory — which exists before the agent has written any state — and finally the project root, the sane thing to act on for an unknown or finished id. -- The events variant differs in one place: for an ended agent whose worktree is gone, its archived log wins over the project's shared journal. +- A session id resolves in order: the live agent's own recorded checkout first; then its worktree directory, which exists before the agent has written any state; finally the project root — the sane thing to act on for an unknown or finished id. +- The events variant differs in one place: for an ended agent whose worktree is gone, its archived log wins over the project root's own journal, so the transcript shown is the agent's own rather than whatever run last wrote at the root. ## Rationales diff --git a/packages/the-framework/src/store/agent-store.SPEC.md b/packages/the-framework/src/store/agent-store.SPEC.md index 728fb3754..a4cec4a3b 100644 --- a/packages/the-framework/src/store/agent-store.SPEC.md +++ b/packages/the-framework/src/store/agent-store.SPEC.md @@ -1,15 +1,22 @@ Agent persistence: an agent's history is its append-only event log, and everything shown about it is folded from that log. +## User Stories + +- The user scrolls a project's history and sees every finished agent — their own and their teammates' — each shown once. +- The user reopens a finished agent and it picks up its own log again, so the continuation stays one history row, not two. +- The user never finds a crashed agent still shown as running, or still asking its last question. +- The user sees facts that land after an agent ended — the pull request opened for its work, the branch a cloud session landed on — appear on its archived row. + ## Flows - Each event is appended to the log and folded into a small snapshot, so a list read costs one file instead of a replay. A restarted dashboard rehydrates by replaying the log itself. - Only orchestration events are stored; the agent's own transcript belongs to the agent. -- On close, a finished agent's log and snapshot are copied into the archive: the data branch's per-user directory, or the transient one when the agent has no worktree of its own. -- Listing a project's history reads every user's archive and the transient one, shows an agent once when it appears in both, and prefers an agent's live copy to its archived one. -- The snapshot is renamed into place rather than written over, so a reader outside the agent's process sees a whole snapshot or the whole previous one. One that arrives unreadable is read again before it is called corrupt. -- An agent whose process is provably dead has its missing ending written on its behalf — into the log as well as the snapshot. An owner that cannot be probed is left alone until boot. -- An archived snapshot can be patched afterwards with a fact discovered once the agent's process is gone, such as the pull request opened for its work or the branch a cloud session's work landed on. -- Ids are timestamps made path-safe, so id order is time order. +- On close, a finished agent's log and snapshot are copied into the archive the dashboard's history lists: the per-user directory on the data branch (`tf-data`), or the untracked in-repo one when the agent has no worktree of its own. +- The history the user scrolls reads every user's archive plus the untracked one, shows an agent once when it appears in both, and prefers an agent's live copy to its archived one. +- A reader outside the agent's process always sees a whole snapshot — the new one or the whole previous one: it is renamed into place, never written over. One that arrives unreadable is read again before it is called corrupt. +- A dead agent never stays running in a listing: once its process is provably gone, the missing ending is written on its behalf — into the log as well as the snapshot. An owner that cannot be probed is left alone until boot. +- A fact discovered once the agent's process is gone — the pull request opened for its work, the branch a cloud session's work landed on — is patched onto the archived snapshot, so the agent's row shows it. +- Ids are timestamps made path-safe, so id order is time order and the history sorts newest-first by id alone. ## Rationales @@ -18,7 +25,7 @@ Agent persistence: an agent's history is its append-only event log, and everythi - The ending is written into the log too, or the agent's last question renders as answerable forever. - An owner on another machine, or one with no record of its process, is cleaned up only at boot: a routine read must not kill an agent another machine is still driving. - Everything here is known by one name: when a file or directory here is renamed, no fallback to the previous name is added, so state left under it reads as absent. -- A record is the right home for a late fact either way, so a surface reading it never has to know which path produced it. +- A late fact is written onto the record itself because every surface already reads the record, so none has to know whether a fact arrived live or after the end. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/store/agent-store.test.SPEC.md b/packages/the-framework/src/store/agent-store.test.SPEC.md index 713525da8..c5b4fb328 100644 --- a/packages/the-framework/src/store/agent-store.test.SPEC.md +++ b/packages/the-framework/src/store/agent-store.test.SPEC.md @@ -1,8 +1,13 @@ -The tests cover the persistence promises: events fold into the same snapshot whether appended live or replayed, agents archive on close and are rescued after a crash, per-user archives on the data branch are listed team-wide and deduplicated, dead agents are healed everywhere they can be found (the missing ending written into log, snapshot, and archive, while live owners and other machines' agents are left alone), and continuing an agent reopens the same log under its original intent. +The tests cover the agent store's persistence promises, from event folding and archiving to crash healing and torn-read safety. ## Flows -- A snapshot is renamed into place rather than written over, so its own path is never the empty file a truncate leaves for a concurrent reader; a store whose filesystem cannot rename still writes in place, and a snapshot that arrives unparseable anyway is read again rather than reported missing — a live agent never blinks out of a listing mid-write — while one that stays unparseable is still given up on. +- Events fold into the same snapshot whether appended live or replayed from the log. +- Agents archive on close and are rescued into the archive after a crash. +- Per-user archives on the data branch (`tf-data`) are listed team-wide and deduplicated. +- Dead agents are healed everywhere they can be found: the missing ending is written into log, snapshot, and archive, while live owners and other machines' agents are left alone. +- Continuing an agent reopens the same log under its original intent. +- A snapshot is renamed into place rather than written over, so its own path never holds the empty file a truncate leaves for a concurrent reader. A store whose filesystem cannot rename still writes in place. A snapshot that arrives unparseable anyway is read again rather than reported missing — a live agent never blinks out of a listing mid-write — while one that stays unparseable is still given up on. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/store/worktree-deps.SPEC.md b/packages/the-framework/src/store/worktree-deps.SPEC.md index 04d0cc41c..8c9c1dd54 100644 --- a/packages/the-framework/src/store/worktree-deps.SPEC.md +++ b/packages/the-framework/src/store/worktree-deps.SPEC.md @@ -1,9 +1,14 @@ Gives a fresh worktree a working dependency tree instantly, by symlinking the parent checkout's installed dependencies instead of copying or reinstalling them. +## User Stories + +- The user's agent starts with working dependencies at once — no install wait, no copied gigabytes. +- The user's PR never carries the dependency links the framework planted in the worktree. + ## Flows - Whole dependency directories are linked — the root's and each workspace package's; an agent that changes the lockfile needs its own install anyway and runs it itself. -- The links are hidden from git through a repo-level exclude, so the agent's sweeping commit cannot drag dangling links onto its branch and the PR. +- The links are hidden from git through a repo-level exclude, so the agent's sweeping `git add -A` commit cannot drag dangling links onto its branch and the PR. - Best-effort throughout: a worktree without dependencies is a worse agent, not a failed one. ## Rationales diff --git a/packages/the-framework/src/store/worktree.SPEC.md b/packages/the-framework/src/store/worktree.SPEC.md index 15189c8db..b21c43b9d 100644 --- a/packages/the-framework/src/store/worktree.SPEC.md +++ b/packages/the-framework/src/store/worktree.SPEC.md @@ -1,11 +1,18 @@ The git-worktree lifecycle behind concurrent agents: each agent gets its own checkout on its own branch, so several on one repo never fight over the working tree. +## User Stories + +- The user runs several agents on one repo at once, and their own checkout is never touched. +- The user sees the branch renamed to match the name the agent picks for itself. +- The user resumes an agent and it continues on the branch its work is already on. +- The user removes an agent's checkout and the uncommitted work survives on its branch. + ## Flows -- An agent's branch is first named after its id (the only name that exists at start) and renamed to its chosen name once the agent picks one — unless the agent already moved to a branch of its own, which is then left alone. -- Continuing an agent re-attaches the branch its work is already on, rather than branching afresh and stranding what it did last time. -- Teardown commits whatever the agent left uncommitted before removing the checkout, so the branch outlives the worktree; the commit retries briefly past a transient lock race. -- Removal tries politely first and forces only as a told-about fallback; a checkout's size read is best-effort, since it only labels a delete button. +- An agent's branch is first named after its id — the only name that exists at start. Once the agent picks a session name, the branch is renamed to match; an agent that already moved to a branch of its own is left alone. +- When the user continues an agent, its new worktree re-attaches the branch its work is already on, rather than branching afresh and stranding what it did last time. +- Teardown commits whatever the agent left uncommitted before removing the checkout, so the branch outlives the worktree. The commit retries briefly past a transient lock race. +- Removal tries git's plain removal first and falls back to forcing, saying so when it does. A checkout's size read is best-effort, since it only labels a delete button. ## Rationales From ac1a920bd529024b73790a3bad3ac09231ca9479 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 08:57:37 +0000 Subject: [PATCH 05/16] Style pass: re-voice the dashboard-rpc specs to the user-story axis Every RPC spec now opens from the click or look it serves, with User Stories naming what the user does; dense bullets split; "backlog" disambiguated; null-prototype and relay behaviors stated observable-first; the recently corrected control-file vs direct-write split preserved byte-identical. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- .../the-framework/src/dashboard-rpc/SPEC.md | 14 +++++++++++--- .../src/dashboard-rpc/context.SPEC.md | 4 ++-- .../src/dashboard-rpc/control.SPEC.md | 19 +++++++++++++++---- .../src/dashboard-rpc/events-tail.SPEC.md | 2 +- .../src/dashboard-rpc/events.SPEC.md | 9 ++++++++- .../src/dashboard-rpc/index.SPEC.md | 3 ++- .../src/dashboard-rpc/preferences.SPEC.md | 12 ++++++++++-- .../src/dashboard-rpc/projects.SPEC.md | 12 +++++++++--- .../src/dashboard-rpc/quota.SPEC.md | 9 ++++++++- .../src/dashboard-rpc/reads.SPEC.md | 17 ++++++++++++----- .../src/dashboard-rpc/relay-dispatch.SPEC.md | 8 +++++++- .../src/dashboard-rpc/stream-forward.SPEC.md | 9 +++++++-- 12 files changed, 92 insertions(+), 26 deletions(-) diff --git a/packages/the-framework/src/dashboard-rpc/SPEC.md b/packages/the-framework/src/dashboard-rpc/SPEC.md index e5cc7e858..d4c4fdee4 100644 --- a/packages/the-framework/src/dashboard-rpc/SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/SPEC.md @@ -1,11 +1,19 @@ The browser's call surface: every dashboard read and write arrives here as a plain HTTP call, and steering an agent means appending a command to that agent's own control file — the same append no matter who asked (a dashboard click, a remote device). +## User Stories + +- The user watches any agent's transcript live and never misses its ending. +- The user steers a live agent — stops it, answers its question, messages it, arms its handoff — and the agent reacts. +- The user publishes a finished agent's work and starts new agents without leaving the dashboard. +- The user watches and steers an agent running on a saved remote device exactly like one running here. + ## Flows -- Reads are thin projections of the files agents and the daemon already write, called by name over plain HTTP. Live events stream over one subscription per agent, tailing that agent's own journal — and when teardown archives the journal mid-stream, the stream follows it and delivers exactly what it had not yet shown, once. -- Writes that steer a live agent — stop, answer a choice, send a message, arm the handoff — are commands appended to the target agent's control file; there is no direct channel into the running process. (A Claude web session has none: its answer is queued for the browser extension to type in.) +- Everything the user looks at is a thin projection of files the agents and the daemon already write, called by name over plain HTTP. +- Watching an agent is one live subscription, tailing that agent's own journal — and when teardown archives the journal mid-stream, the stream follows it and delivers exactly what it had not yet shown, once. +- When the user steers a live agent — stop, answer a choice, send a message, arm the handoff — the write is a command appended to the target agent's control file; there is no direct channel into the running process. (A Claude web session has none: its answer is queued for the browser extension to type in.) - The other writes act directly: push, open a PR, and merge run the git handoff on the agent's branch, while starting an agent and queueing a ticket go through the daemon's own wiring. -- Two routing decisions live here and nowhere else: which checkout an agent-scoped call resolves to (the agent's own, else the project root), and whether the call is local or belongs to an agent relayed to a connected device — forwarded there against a deliberate allowlist that swaps in the device's own home project. +- Two routing decisions live here and nowhere else: which checkout an agent-scoped call resolves to (the agent's own, else the project root), and whether the agent a call names is local or relayed to a connected device — a relayed one's call is forwarded there, against a deliberate allowlist that swaps in the device's own home project. - One surface, one host. The daemon wires every capability at start-up and the calls read it as simply there; an unwired one is a bug that says which field is missing, not a degraded mode to render around. ```mermaid diff --git a/packages/the-framework/src/dashboard-rpc/context.SPEC.md b/packages/the-framework/src/dashboard-rpc/context.SPEC.md index c614988ad..a730757f7 100644 --- a/packages/the-framework/src/dashboard-rpc/context.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/context.SPEC.md @@ -3,8 +3,8 @@ How each dashboard call reaches the one host's capabilities, and which checkout ## Flows - One host wires everything at start-up, so a capability is simply there; a missing one is a wiring bug that says which capability is missing. -- The one exception is "is this agent relayed onward?", which defaults to no: a call arriving over the device relay runs outside a request, and the agent it names is local to that device — forwarding it again would be a loop. -- A call that names an agent resolves to that agent's own checkout — where it actually reads, writes, and listens — falling back to the project root only for one that has none. +- One lookup defaults instead of throwing: "is this agent relayed onward?" answers no when unwired. A call arriving over the device relay runs outside a request, and the agent it names is local to that device — forwarding it again would be a loop. +- A call that names an agent resolves to that agent's own checkout — where it actually reads, writes, and listens — falling back to the project root only for an agent that has none. Anywhere else the call would read an empty journal and steer an agent that is not listening. ## Rationales diff --git a/packages/the-framework/src/dashboard-rpc/control.SPEC.md b/packages/the-framework/src/dashboard-rpc/control.SPEC.md index 1d4355cba..94cd52fc5 100644 --- a/packages/the-framework/src/dashboard-rpc/control.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/control.SPEC.md @@ -1,12 +1,23 @@ Every dashboard action that changes something: steering a live agent, starting one, and publishing or cleaning up what a finished one left. +## User Stories + +- The user steers a live agent: Stop, answer its choice card, send it a chat message, change how far it publishes itself when it finishes, arm Merge. +- The user answers the question a Claude web session is parked on, and the browser extension types the pick into claude.ai. +- The user publishes what a finished agent left: push its branch, open a pull request, merge. +- The user starts an agent and opens a checkout in their editor. +- The user removes a kept checkout, or deletes an agent for good. +- The user queues a ticket for the agents and frees a stuck ticket claim by hand. + ## Flows -- Steering (stop, answer a choice, send a message, arm the handoff, merge) appends a command to the agent's own control file, which the agent watches — the same append whoever asks, no direct line into the process. A Claude web session has none to steer: its answer is queued for the browser extension to type in, and only as a label of the question actually parked. +- When the user steers a live agent — stop, answer a choice, send a message, arm the handoff, merge — the click appends a command to the agent's own control file, which the agent watches: the same append whoever asks, no direct line into the process. +- A Claude web session has no local process to steer: the user's pick is queued for the browser extension to type into claude.ai, and only as a label of the question actually parked. - Starting an agent and opening a checkout in an editor call straight into the daemon's own wiring: there is one host and it wires everything, so a missing capability is a wiring bug that names itself rather than a state a request can find. -- Publishing a finished agent — push its branch, open a PR, merge — first commits what it left uncommitted, and holds a lock across the commit *and* the push, since teardown publishes the same branch under the same lock: a click racing it must neither lose the work nor collide creating the ref. Merge steers a live agent to merge at its natural end, and merges a finished one's PR directly. -- Removing a kept checkout or deleting an agent refuses while it is live, saves the work as a commit, and stops any preview serving the tree first. -- Queueing a ticket writes the project's backlog directly, under the ticket's priority and linking back to it; a stuck ticket claim can be released by hand. +- When the user publishes a finished agent — push its branch, open a PR, merge — the work it left uncommitted is committed first, and a lock is held across the commit *and* the push, since teardown publishes the same branch under the same lock: a click racing it must neither lose the work nor collide creating the ref. +- Merge is one button for two states: it steers a live agent to merge at its natural end, and merges a finished one's PR directly. +- When the user removes a kept checkout or deletes an agent, the action refuses while the agent is live, saves the work as a commit, and stops any preview serving the tree first. +- When the user queues a ticket, the entry is written straight into the project's backlog, under the ticket's priority and linking back to the ticket, so the next agent working the queue picks it up; a stuck ticket claim can be released by hand. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard-rpc/events-tail.SPEC.md b/packages/the-framework/src/dashboard-rpc/events-tail.SPEC.md index c672179c5..7c5872606 100644 --- a/packages/the-framework/src/dashboard-rpc/events-tail.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/events-tail.SPEC.md @@ -2,7 +2,7 @@ Follows an agent's on-disk event journal: replay what is already logged, then de ## Flows -- Each logged event is handed over once, with a one-time signal after the backlog — the boundary a reconnecting viewer needs to swap its feed atomically instead of blanking while history re-streams. +- Each logged event is handed over once, and a one-time signal marks the moment everything already logged has been delivered — the boundary a reconnecting viewer needs to swap its feed atomically instead of blanking while history re-streams. - A journal does not sit still: teardown copies it into the archive and removes the checkout. "The file existed and is now gone" is treated as that move — the tail asks where the journal lives now and picks up there at the same position, so the ending's final lines arrive exactly once instead of being swallowed. - While the new home is not yet visible (or never will be — a deleted agent), the tail idles rather than hopping somewhere wrong. - A file watcher with a slow poll behind it, so a missed filesystem signal delays events rather than losing them. diff --git a/packages/the-framework/src/dashboard-rpc/events.SPEC.md b/packages/the-framework/src/dashboard-rpc/events.SPEC.md index 18ddc14a8..8c59170d9 100644 --- a/packages/the-framework/src/dashboard-rpc/events.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/events.SPEC.md @@ -1,11 +1,18 @@ Streams one agent's events live to the browser: everything already logged is replayed, then each new event as it happens. +## User Stories + +- The user watches an agent's transcript live — history first, then every new event as it happens. +- The user sees the agent's ending even when it finishes mid-watch. +- The user reloads the page and the transcript swaps in whole instead of blanking and refilling. +- The user watches an agent running on a saved remote device the same way. + ## Flows - The agent id picks whose journal to follow — each agent logs in its own checkout — so the feed is that agent's alone; without it, the project root's journal is followed, which is only right for an agent that has no checkout. - The tail follows the journal when teardown archives it mid-stream, so a watcher never misses the ending — but an agent-scoped feed never falls back to the project-root journal, which is another agent's story: a deleted agent's tab goes quiet instead. - After the replay, a one-time caught-up marker lets a reconnecting viewer swap its feed whole instead of blanking and refilling; the marker travels only on the wire, never into any journal. -- An agent with no file on this host — one relayed from a device — streams from memory instead. +- An agent with no file on this host — one relayed from a device — streams from memory instead: no caught-up marker (there is no on-disk replay to bound), and the feed ends when that agent finishes rather than staying open like a live feed gone quiet. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard-rpc/index.SPEC.md b/packages/the-framework/src/dashboard-rpc/index.SPEC.md index c412b3c13..3b5ba78b2 100644 --- a/packages/the-framework/src/dashboard-rpc/index.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/index.SPEC.md @@ -3,7 +3,8 @@ The single doorway to the dashboard's call surface: everything the browser can a ## Flows - The table of callable names is built from the modules' own exports, so a call that exists but was never registered — a 400 with nothing to go on — is not a state this can be in: the name *is* the export name. -- It inherits nothing. The name is a path segment off an unauthenticated request, and a table with the usual object behind it answers to `constructor` and its siblings, which are not RPCs. +- A name that is not an RPC gets a 404 — `constructor` and its prototype siblings included. The table inherits nothing, because the name is a path segment off an unauthenticated request, and a table with the usual object behind it would answer those inherited names as if they were calls. +- The live event stream rides beside the table as a subscription rather than a call. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard-rpc/preferences.SPEC.md b/packages/the-framework/src/dashboard-rpc/preferences.SPEC.md index 95dee23b0..5bc486ede 100644 --- a/packages/the-framework/src/dashboard-rpc/preferences.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/preferences.SPEC.md @@ -1,10 +1,18 @@ Reading and saving the user's settings: the dashboard preferences, a project's shared presets, the editor picker, and the Discord credentials. +## User Stories + +- The user changes a setting once and it sticks — across tabs, reloads, and daemon restarts. +- The user saves a preset into the repo and the whole team gets it. +- The user picks a preferred editor from the ones actually installed on the server. +- The user sets the Discord credentials from the settings page and the bot connects right away. + ## Flows -- Settings live with the dashboard, not the browser, so they survive restarts and follow the user across tabs. A write that fails answers with the reason rather than rejecting, so the client renders it instead of losing the save. +- Settings live on the daemon, not in the browser, so they survive restarts and follow the user across tabs. A write that fails answers with the reason rather than rejecting, so the client renders it instead of losing the save. - A save can merge only the keys a tab changed and hand back what is now stored, so a stale tab converges instead of reverting settings it never touched. -- A project's shared presets are committed into the repo itself, so they travel with the team rather than with one user. +- A project's shared presets are saved into the repo itself, so they travel with the team rather than with one user. +- The editor picker lists the editors actually installed on this server. - Discord credentials are write-only: the browser can set them and learn that they exist (and where each came from), never read a value back; a save applies live, so the bot connects without a restart. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard-rpc/projects.SPEC.md b/packages/the-framework/src/dashboard-rpc/projects.SPEC.md index 55de534f4..4a9c9217d 100644 --- a/packages/the-framework/src/dashboard-rpc/projects.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/projects.SPEC.md @@ -1,10 +1,16 @@ The project list and the launcher's pre-flight answers: which projects are registered, adding new ones, and whether an agent started now would get anywhere. +## User Stories + +- The user sees every registered project, and a project in trouble wears its error right in the sidebar and on its page. +- The user adds a project from the dashboard — one repo, or every repo under a folder. +- The user is warned before a doomed start: a merge armed on a repo that cannot auto-merge, a driver CLI that is missing or logged out. + ## Flows -- Each listed project carries what the daemon's background jobs currently find wrong with it — a data branch that cannot reach origin, say — so the one list every project surface already polls is also how an error reaches the sidebar and the project's page. -- Adding a project (one repo, or every repo under a folder) goes through the daemon so it lands in the shared registry; the onboarding hint offers the daemon's own directory as the first project. -- The pre-flight reads warn before a doomed start rather than after: whether the repo allows auto-merge (an armed merge otherwise lands before CI has run), and whether the chosen driver's CLI is installed and logged in — reporting only problems the user can act on, never account details a visitor on a network-bound host has no business seeing. +- Each listed project carries what the daemon's background jobs currently find wrong with it — a data branch that cannot reach origin — and the one list every project surface already polls is how that error reaches the sidebar's red dot and the project page's banner. +- When the user adds a project (one repo, or every repo under a folder), the daemon installs and registers it, so it lands in the shared registry; the onboarding hint offers the daemon's own directory as the first project. +- The launcher — the form that starts an agent — warns before a doomed start rather than after: whether the repo allows auto-merge (an armed merge otherwise lands before CI has run), and whether the chosen driver's CLI is installed and logged in. Only problems the user can act on are reported — never account details a visitor on a network-bound host has no business seeing. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard-rpc/quota.SPEC.md b/packages/the-framework/src/dashboard-rpc/quota.SPEC.md index 33fb4101e..8eb2583e4 100644 --- a/packages/the-framework/src/dashboard-rpc/quota.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/quota.SPEC.md @@ -1,9 +1,16 @@ The usage panel's answers: where the account's quota stands, what the background PM last decided, and a way to make it sweep right now. +## User Stories + +- The user reads where the account's quota stands — and a failed reading says so instead of drawing an empty bar. +- The user reads what the automatic PM last decided, project by project. +- The user makes the PM sweep right now instead of waiting for its next interval. + ## Flows - No reading is reported as no reading, never as an empty bar — an empty bar reads as "nothing used", the one thing this panel must never imply. -- A hand-fired sweep runs even with the automatic PM switched off: that preference is consent to spend quota unasked, and this click is asking. The call waits for the sweep and returns what it decided, project by project, so the card can say it without racing a poll — and it can be narrowed to only working the queue. +- What the automatic PM — the background project manager that starts unattended work — last decided is read beside the quota, since it spends against exactly that boundary; a loop with nothing to report yet reads as "nothing to say", never as an idle sweep. +- When the user fires a sweep by hand, it runs even with the automatic PM switched off: that preference is consent to spend quota unasked, and this click is asking. The call waits for the sweep and returns what it decided, project by project, so the card can say it without racing a poll — and the sweep can be narrowed to only working the queue. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard-rpc/reads.SPEC.md b/packages/the-framework/src/dashboard-rpc/reads.SPEC.md index fe40bed56..a8b30a33d 100644 --- a/packages/the-framework/src/dashboard-rpc/reads.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/reads.SPEC.md @@ -1,12 +1,19 @@ Every read the dashboard makes: agents and their history, docs, tickets and queues, cross-project rollups, files and diffs, and where a finished agent's work stands. +## User Stories + +- The user sees every project's agents and their history, docs, tickets, queues, and cross-project rollups in one dashboard. +- The user browses an agent's files, diffs, and git state and sees exactly what that agent changed — no other agent's, and no predecessor's. +- The user sees where a finished agent's work stands: the branch it left, and what is committed, pushed, or opened as a PR. +- The user follows a Claude web session through the browser extension's eyes: its parked question and the fate of the picked answer. + ## Flows -- Everything is a projection of files the agents and daemon already write — the dashboard holds no state of its own — and an unknown project or a host with no checkout gets empty results, never errors. -- The agent list merges archived, live, and device-relayed agents; a live copy wins a tie, so a continued agent reads as running rather than as its finished first leg, and a relayed one survives a reload despite existing only in memory. -- Agent-scoped reads (files, diffs, git status, what changed) look at that agent's own checkout and are filtered to its lifetime, so an agent on a reused branch never wears a predecessor's PR; changes come from git, not from watching the agent's tools — verify by outcome. -- The handoff read asks the agent's branch — the thing that outlives it — taking only uncommitted work from the checkout, and only when that checkout is genuinely the agent's own. -- The bridge reads report what the browser extension saw of a Claude web session: its parked question, the picked answer's fate, and whether the extension has made contact at all. +- Everything the user sees is a projection of files the agents and the daemon already write — the dashboard holds no state of its own. An unknown project or a host with no checkout gets empty results, never errors. +- The agent list merges archived, live, and device-relayed agents. A live copy wins a tie, so a continued agent reads as running rather than as its finished first leg; a relayed agent survives a reload despite existing only in the daemon's memory. +- What the user browses of an agent — files, diffs, git status, what changed — is read from that agent's own checkout and filtered to its lifetime, so an agent on a reused branch never wears a predecessor's PR. Changes come from git, not from watching the agent's tools: verify by outcome. +- Where a finished agent's work stands is asked of the agent's branch — the thing that outlives it; only uncommitted work is taken from the checkout, and only when that checkout is genuinely the agent's own. +- The bridge reads report what the browser extension watching claude.ai saw of a Claude web session: its parked question, the picked answer's fate, and whether the extension has made contact at all. The token the user pastes into the extension during setup is handed out only while the bridge is switched on. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard-rpc/relay-dispatch.SPEC.md b/packages/the-framework/src/dashboard-rpc/relay-dispatch.SPEC.md index a1b22309f..876fcbf22 100644 --- a/packages/the-framework/src/dashboard-rpc/relay-dispatch.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/relay-dispatch.SPEC.md @@ -1,8 +1,14 @@ The device side of the remote-agent relay: executes a forwarded call against this device's own home project, and only calls on a fixed allowlist. +## User Stories + +- The user runs an agent on this machine (a saved remote device) while their dashboard runs on another — and still reads the agent's transcript, files, and git state from there. +- The user steers and publishes that remote agent from their dashboard — stop, answer, message, push, open a PR, merge — and each click executes here. + ## Flows -- The caller's project id is discarded and replaced with the device's own home project, so a forwarded call can only ever address the device's home checkout — never another project registered on it. +- A forwarded call arrives naming the dashboard machine's project id, which means nothing here; it is discarded and replaced with the device's own home project, so a forwarded call can only ever address the device's home checkout — never another project registered on it. +- Each allowlisted name is the very RPC this device's own dashboard exposes, resolved through the same project registry, so a relayed click behaves exactly like a local one. - The allowlist is exactly the agent-scoped read/steer/handoff surface; starting agents, previews, and anything that destroys history or checkouts stays off it. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard-rpc/stream-forward.SPEC.md b/packages/the-framework/src/dashboard-rpc/stream-forward.SPEC.md index d66b51307..4d6b6a7cf 100644 --- a/packages/the-framework/src/dashboard-rpc/stream-forward.SPEC.md +++ b/packages/the-framework/src/dashboard-rpc/stream-forward.SPEC.md @@ -1,8 +1,13 @@ -Plumbing that pumps an in-memory event source — an agent relayed from a device, replaying its buffered history then following live — into a plain callback, stopping when the viewer leaves and saying so when the source runs out on its own. +The pump behind a relayed agent's live feed: it moves the agent's in-memory event stream — buffered history first, then live — into a plain callback, one call per event. + +## Flows + +- The viewer leaving stops the pump and cancels the source, so nothing keeps following an unwatched agent. +- A source that runs out on its own — a relayed agent that finished — fires a done signal, so the viewer's feed closes instead of staying open like a live agent gone quiet. ## Rationales -- Transport-agnostic on purpose: what a value becomes on the wire is the mount's business, which is what lets this be driven and tested by itself. +- Transport-agnostic on purpose: what a value becomes on the wire is the business of the HTTP layer that mounts the stream, which is what lets this pump be driven and tested by itself. ## Before modifying/creating SPEC.md files From 561446d3b0ac72c8507e21885c4f3c98993f15e3 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 09:00:27 +0000 Subject: [PATCH 06/16] Style pass: re-voice src quota, presets, tickets, and prompt specs to the user-story axis User Stories on the guarantees the user relies on (never starved by quota, no double-worked tickets, exact prompt readable, refusal-with-fix before spend); flows opened from the user's side; jargon replaced with the named ladder or glossed; preflight's "once, by the dashboard" corrected to the code-accurate pre-spawn framing. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/src/loopback-host.SPEC.md | 4 ++-- packages/the-framework/src/maintenance.SPEC.md | 7 ++++++- packages/the-framework/src/merged-worktrees.SPEC.md | 10 ++++++++-- .../src/on-before-mergeable-prompt.SPEC.md | 8 +++++++- packages/the-framework/src/preference-defaults.SPEC.md | 4 ++++ packages/the-framework/src/preflight.SPEC.md | 9 +++++++-- packages/the-framework/src/preset-catalog.SPEC.md | 4 ++++ packages/the-framework/src/preset-prompt.SPEC.md | 4 ++++ packages/the-framework/src/presets.SPEC.md | 4 ++++ packages/the-framework/src/project-errors.SPEC.md | 5 +++++ packages/the-framework/src/project-presets.SPEC.md | 4 ++++ packages/the-framework/src/project.SPEC.md | 2 +- packages/the-framework/src/prompt-template.SPEC.md | 2 +- packages/the-framework/src/quota-boundary.SPEC.md | 7 ++++++- packages/the-framework/src/quota-poller.SPEC.md | 6 +++++- packages/the-framework/src/registry.SPEC.md | 7 ++++++- packages/the-framework/src/stale-branch.SPEC.md | 4 ++++ packages/the-framework/src/steps.SPEC.md | 5 +++++ packages/the-framework/src/system-prompt.SPEC.md | 6 ++++++ packages/the-framework/src/terminal.SPEC.md | 5 +++++ packages/the-framework/src/ticket-locks.SPEC.md | 7 ++++++- packages/the-framework/src/tickets.SPEC.md | 6 +++--- packages/the-framework/src/todo-loop.SPEC.md | 6 ++++-- packages/the-framework/src/turn-gate.SPEC.md | 5 +++++ 24 files changed, 112 insertions(+), 19 deletions(-) diff --git a/packages/the-framework/src/loopback-host.SPEC.md b/packages/the-framework/src/loopback-host.SPEC.md index ae0939eb8..728c5461e 100644 --- a/packages/the-framework/src/loopback-host.SPEC.md +++ b/packages/the-framework/src/loopback-host.SPEC.md @@ -2,8 +2,8 @@ Decides whether an address is truly local, so the dashboard skips its access tok ## Flows -- A bind-all or routable address is not local and stays gated behind the shared token. -- A registrable name that merely starts with "127." is rejected — that is exactly the rebound host a DNS-rebinding attack uses to look local. +- A bind-all address (`0.0.0.0`, `::`) or a routable one is not local and stays gated behind the shared token. +- A DNS name that merely starts with `127.` is rejected — a name someone can register is exactly the rebound host a DNS-rebinding attack uses to look local. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/maintenance.SPEC.md b/packages/the-framework/src/maintenance.SPEC.md index 0e76ca4a5..118fb3d16 100644 --- a/packages/the-framework/src/maintenance.SPEC.md +++ b/packages/the-framework/src/maintenance.SPEC.md @@ -1,10 +1,15 @@ The maintenance sweep: a background job that keeps registered repos healthy by running the maintainability review on whatever new work each repo has grown since it was last reviewed. +## User Stories + +- The user's registered repos get a maintainability review of whatever new work they grow, with nobody at the keyboard. +- The user who installs the tool on a repo with years of history is never flooded with reviews of old code. + ## Flows - Each repo remembers its last-reviewed commit in a small local file, so a sweep only ever acts on commits added since then. - A repo seen for the first time is baselined at its current state — its pre-existing history is never reviewed retroactively. -- A separate weekly schedule triggers a whole-codebase pass that ignores commits entirely, closing the gap baselining leaves for repos that adopted the tool late; the two schedules are stored side by side so neither resets the other. +- A separate weekly schedule triggers a whole-codebase pass that ignores commits entirely — it closes the gap baselining leaves for repos that adopted the tool late. The two schedules are stored side by side, so neither resets the other. - A review counts as done only when it succeeds, so failures are retried next sweep; a sweep can be capped to a few repos, reporting the rest as pending. ## Rationales diff --git a/packages/the-framework/src/merged-worktrees.SPEC.md b/packages/the-framework/src/merged-worktrees.SPEC.md index 3266cc982..101ebc7dc 100644 --- a/packages/the-framework/src/merged-worktrees.SPEC.md +++ b/packages/the-framework/src/merged-worktrees.SPEC.md @@ -1,9 +1,14 @@ Automatically reclaims an agent's checkout once its work is on the remote — the branch, the commits, and the agent's history are kept, so only disk is freed. +## User Stories + +- The user gets the disk back from ended agents without doing anything — the branch, its commits, and the agent's history all stay. +- The user learns why a checkout stayed, once per reason, not once every sweep. + ## Flows -- One rule decides every removal, shared with the dashboard's manual Remove button so the automatic path and the manual one cannot disagree: the work is committed to the agent's branch, the branch is pushed, and the checkout goes only once the remote has it. Every deletion is therefore recoverable, because the remote holds a copy. -- The sweep is what reaches the agents the teardown could not: a push that failed then (offline, no auth, a rejected non-fast-forward) simply succeeds on a later pass. There is one failure mode, and it is said out loud — once per kept checkout per reason, not once per pass: a checkout that can never be reclaimed would otherwise repeat the same line every ten minutes for as long as the daemon runs, while a reason that changes is a changed state and is said again. +- One rule decides every removal, and the dashboard's manual Remove button shares it, so the automatic path and the manual one cannot disagree: the work is committed to the agent's branch, the branch is pushed, and the checkout goes only once the remote has it. Every deletion is therefore recoverable, because the remote holds a copy. +- The sweep is what reaches the agents the teardown could not: a push that failed then (offline, no auth, a rejected non-fast-forward) simply succeeds on a later pass. There is one failure mode — the push did not land — and the user is told, once per kept checkout per reason, not once per pass; a reason that changes is a changed state and is said again. - Whether the repo has a remote at all is asked once per project: with none, every checkout is kept and accounted for without probing each one for a push that cannot land. - A live agent keeps its checkout: its driver is working in there, and Stop is how one ends. So does one the daemon has not finished retiring — an agent's records say `done` a beat before its teardown reclaims the checkout, and "not live" is not "nobody is holding this". - Every project is swept once at startup and every ten minutes after, on the daemon's shared clock rather than a timer of its own, and each removal is announced so it never reads as a bug. @@ -11,6 +16,7 @@ Automatically reclaims an agent's checkout once its work is on the remote — th ## Rationales - The one rule asks *is the work safe yet* rather than *how did this agent end*: one predicate, checkable at any moment, with one failure mode — where per-ending rules (a clean finish removes the checkout, a failure or stop keeps it, a merged branch reclaims it later through separate "landed" signals) leave the kept checkouts accumulating, one per failed agent, forever. +- The kept-checkout report is deduplicated by reason because a checkout that can never be reclaimed would otherwise repeat the same line every ten minutes for as long as the daemon runs. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/on-before-mergeable-prompt.SPEC.md b/packages/the-framework/src/on-before-mergeable-prompt.SPEC.md index 3ceabee9a..ed9cd8785 100644 --- a/packages/the-framework/src/on-before-mergeable-prompt.SPEC.md +++ b/packages/the-framework/src/on-before-mergeable-prompt.SPEC.md @@ -1,9 +1,15 @@ The prompt sent to an agent once its work is ready to merge: queue the quality reviews as follow-up work, and fold what it learned back into the project's business-knowledge docs. +## User Stories + +- The user finds quality follow-ups queued after an agent's work is ready to merge, instead of review agents running on the spot. +- The user's business-knowledge docs grow what the finished agent decided and learned. + ## Flows -- It queues the quality presets (maintainability and security audit) onto the project's backlog rather than running them on the spot; the backlog loop picks them up later. +- The agent judges its own finished changes and queues the matching quality presets onto the project's queue file (`TODO_AGENTS.md`) rather than running them on the spot: refactor potential queues the maintainability review, possible security impact queues the security audit. The backlog loop picks the entries up later. - Every queued entry targets the changes introduced by the finished agent, named explicitly. +- The prompt also asks the agent to fold what the session decided and learned into the project's business-knowledge docs (`knowledge-base/DECISIONS.md`, `FACTS.md`, `INSIGHTS.md`), creating them when missing — and to write only what a future agent needs and cannot get from the code itself. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/preference-defaults.SPEC.md b/packages/the-framework/src/preference-defaults.SPEC.md index 844cb312a..0efda3887 100644 --- a/packages/the-framework/src/preference-defaults.SPEC.md +++ b/packages/the-framework/src/preference-defaults.SPEC.md @@ -1,5 +1,9 @@ The shared defaults and bounds for user preferences, written down once so the dashboard and the daemon act on the same values. +## User Stories + +- The user gets the browser bell and "needs you" alerts without configuring anything; Discord and plain-activity notifications stay silent until switched on. + ## Flows - Notifications are a 2×2 with the axes named: *how* one reaches you (browser, Discord) and *what it is about* (needs-you, plain activity). Delivering a cell asks one question: both its method and its category must be on. diff --git a/packages/the-framework/src/preflight.SPEC.md b/packages/the-framework/src/preflight.SPEC.md index 28f187052..d79b33200 100644 --- a/packages/the-framework/src/preflight.SPEC.md +++ b/packages/the-framework/src/preflight.SPEC.md @@ -1,9 +1,14 @@ Pre-start checks so an agent is refused early and clearly when a prerequisite is missing, instead of spawning a broken one. +## User Stories + +- The user is refused at Start, with the fix named, when the picked CLI is missing or logged out — before a branch or checkout is spent on an agent that cannot run. +- The user who armed publishing is warned — not blocked — when the GitHub CLI is missing or logged out. + ## Flows -- The checks run once, by the dashboard, before it spawns anything — the agent itself does not check again. -- Verifies the driver CLI the agent actually picked is installed and logged in — installed is not usable: a logged-out CLI kills every agent before it starts, while the machine keeps spending branches and checkouts on each attempt. +- The checks run on the framework's side before anything is spawned — the spawned agent never re-checks. +- The driver CLI the agent actually picked is verified installed and logged in — installed is not usable: a logged-out CLI kills every agent before it starts, while the machine keeps spending branches and checkouts on each attempt. - Only a clear "not logged in" blocks; a CLI that will not say gets the benefit of the doubt, because an agent that might work beats a refusal we cannot stand behind. - When the agent will publish (PR/merge), the GitHub CLI is checked too — as warnings only, since its own work needs no GitHub and only publishing would degrade. - Running as root warns without blocking (a container legitimately is root): under sudo the driver looks for credentials in the wrong home and every agent dies with a log that says nothing about why. diff --git a/packages/the-framework/src/preset-catalog.SPEC.md b/packages/the-framework/src/preset-catalog.SPEC.md index 0e0c5dcb4..c8d733279 100644 --- a/packages/the-framework/src/preset-catalog.SPEC.md +++ b/packages/the-framework/src/preset-catalog.SPEC.md @@ -1,5 +1,9 @@ The one table of every built-in preset — the prompts the product offers as one-click agents, from quality reviews to the product-management cluster. +## User Stories + +- The user picks a preset from the launcher menu — a quality review aimed at a target, or the PM cluster aimed at the repo's own tickets and queue — and gets an agent with no prompt to write. + ## Flows - The quality reviews (research, maintainability, readability, security audit, UX) take a target, defaulting to the launching agent or the whole codebase. diff --git a/packages/the-framework/src/preset-prompt.SPEC.md b/packages/the-framework/src/preset-prompt.SPEC.md index 0d2353c6b..00032bb86 100644 --- a/packages/the-framework/src/preset-prompt.SPEC.md +++ b/packages/the-framework/src/preset-prompt.SPEC.md @@ -1,5 +1,9 @@ How a preset is defined: only what actually differs between presets (name, prompt, what its one target means) is declared; parameter handling, defaults, and rendering are shared. +## User Stories + +- The user launches a preset with its target left blank, and it runs against the agent it was launched from — or the whole codebase when there is none. + ## Flows - A preset either takes one target ("what to run against") or scopes itself, in which case its prompt is used verbatim. diff --git a/packages/the-framework/src/presets.SPEC.md b/packages/the-framework/src/presets.SPEC.md index 8aba09e66..cc7d35e69 100644 --- a/packages/the-framework/src/presets.SPEC.md +++ b/packages/the-framework/src/presets.SPEC.md @@ -1,5 +1,9 @@ Writes the built-in presets into a project as real files, so queued follow-up work can point the agent at a preset it can open. +## User Stories + +- The user finds each built-in preset as a real markdown file in the project (`.the-framework/presets/`), so a queued entry naming one points at a file that opens. + ## Flows - Derived from the one preset table, so a preset exists on disk exactly when it exists in the catalog. diff --git a/packages/the-framework/src/project-errors.SPEC.md b/packages/the-framework/src/project-errors.SPEC.md index 5a5987d61..dd7b35b9d 100644 --- a/packages/the-framework/src/project-errors.SPEC.md +++ b/packages/the-framework/src/project-errors.SPEC.md @@ -1,5 +1,10 @@ The daemon's per-project error state: when a background job finds a project in a state only the user can fix, it records the error here, and clears it the moment the state is good again — the dashboard shows whatever is recorded. +## User Stories + +- The user sees a project whose data cannot reach origin flagged in the dashboard, with the failing command's own words and how long it has been that way. +- The user fixes the cause and the flag clears itself on the next sync that converges. + ## Flows - One slot per project and kind of error, holding the detail and when it was first seen; a repeat report of the same kind refreshes the detail but keeps the first-seen time, so the dashboard can say how long the project has been in that state. diff --git a/packages/the-framework/src/project-presets.SPEC.md b/packages/the-framework/src/project-presets.SPEC.md index a8ef8868a..8ca8664a6 100644 --- a/packages/the-framework/src/project-presets.SPEC.md +++ b/packages/the-framework/src/project-presets.SPEC.md @@ -1,5 +1,9 @@ Project-shared custom presets: saved into the repo rather than the user's home file, so a team's presets travel with the code and everyone who clones it gets them. +## User Stories + +- The user saves a custom preset into the repo, and every teammate who clones the repo gets it. + ## Flows - Same shape and same sanitizer as the personal presets, so the dashboard renders both alike and a hand-edited or hostile file is cleaned on read and on write. diff --git a/packages/the-framework/src/project.SPEC.md b/packages/the-framework/src/project.SPEC.md index 828ce2c8f..3713fca62 100644 --- a/packages/the-framework/src/project.SPEC.md +++ b/packages/the-framework/src/project.SPEC.md @@ -2,7 +2,7 @@ Read-only project helpers: whether a repo has The Framework installed, listing i ## Flows -- A repo counts as activated when the ignore file the install writes exists — the file that keeps the framework's transient state off the repo's branches. Writing it is a separate concern. +- A repo counts as activated when the ignore file the install writes exists — the file that keeps the framework's transient state off the repo's branches. This only checks; the install writes it. - The file crawl lists everything git sees (tracked and untracked, honoring ignores) and yields nothing rather than failing. - Git operations get one of three time budgets: read, local write, or network/whole-checkout. - "Is this a git repo at all" is answered separately, so a project that cannot host agents is told apart from git failing. diff --git a/packages/the-framework/src/prompt-template.SPEC.md b/packages/the-framework/src/prompt-template.SPEC.md index 4e8358292..96cefadb6 100644 --- a/packages/the-framework/src/prompt-template.SPEC.md +++ b/packages/the-framework/src/prompt-template.SPEC.md @@ -4,7 +4,7 @@ Renders the built-in system prompt's template by evaluating the small code expre - A fragment that fails or comes out undefined (almost always a typo) stops with a loud error naming the fragment, instead of silently degrading the prompt. - Fragments are real executable code, so only the trusted built-in prompt is ever rendered this way — never user- or repo-supplied text. -- A fragment ends at the first *adjacent* `}}`, so a nested brace must never close right against another one — a space between them is the whole fix, and without it the expression is cut short and fails on a syntax error rather than doing something subtly wrong. +- A fragment ends at the first *adjacent* `}}`, so a nested brace must never close right against another one — a space between them is the whole fix. Without the space, the expression is cut short and fails on a syntax error rather than doing something subtly wrong. ## Rationales diff --git a/packages/the-framework/src/quota-boundary.SPEC.md b/packages/the-framework/src/quota-boundary.SPEC.md index c2843e99b..3bc8c7752 100644 --- a/packages/the-framework/src/quota-boundary.SPEC.md +++ b/packages/the-framework/src/quota-boundary.SPEC.md @@ -1,10 +1,15 @@ Decides how much of the account's weekly allowance The Framework may have spent by now: the share of the week that has already elapsed. +## User Stories + +- The user's asked-for work is never starved by the quota; only unattended work stands down when the week's spend passes its pro-rated share. +- The user moves one slider to give unattended work more or less headroom — there is nothing else to configure. + ## Flows - One policy, nothing to configure: the boundary rises continuously with the clock and reaches the full allowance exactly as the week resets, so a quiet week still gets spent instead of expiring. - Work the user asks for may borrow ahead; unattended work stands down once usage passes the limit — the boundary plus an optional user-set offset (by default a small cushion beyond it). -- Where the week stands is recovered from the agent's own prose about when the quota resets; prose that cannot be placed means "we don't know", never a boundary of zero. +- Where the week stands is recovered from the reset time the coding agent prints as prose; prose that cannot be placed means "we don't know", never a boundary of zero. - Both the account's overall week and the selected model's own week are measured, and whichever reaches the limit first is what stops the work. ## Rationales diff --git a/packages/the-framework/src/quota-poller.SPEC.md b/packages/the-framework/src/quota-poller.SPEC.md index bdc4fa856..78fd11549 100644 --- a/packages/the-framework/src/quota-poller.SPEC.md +++ b/packages/the-framework/src/quota-poller.SPEC.md @@ -1,5 +1,9 @@ Keeps a recent reading of the account's quota on hand, polling slowly and backing off on failure so the number is available without being refused upstream. +## User Stories + +- The user's usage bar always shows a recent reading — an upstream blip never blanks it into "nothing used". + ## Flows - Polling is deliberately slow and backs off on failure rather than retrying into the refusal. @@ -8,7 +12,7 @@ Keeps a recent reading of the account's quota on hand, polling slowly and backin ## Rationales -- Reading the quota is expensive (it launches the whole agent) and gets refused when asked too often — an eager retry loop would keep the number permanently unavailable. +- Reading the quota is expensive (it spawns the coding agent's own CLI) and gets refused when asked too often — an eager retry loop would keep the number permanently unavailable. - The last good reading is retained through blips because a usage bar going empty would read as "nothing used", the one thing this must never imply. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/registry.SPEC.md b/packages/the-framework/src/registry.SPEC.md index 2917a467b..85f6e564d 100644 --- a/packages/the-framework/src/registry.SPEC.md +++ b/packages/the-framework/src/registry.SPEC.md @@ -1,11 +1,16 @@ The user's one home file: the projects The Framework is installed into, the user's dashboard preferences, the daemon's access token, and third-party credentials. +## User Stories + +- The user's dashboard preferences persist on the machine and hold in every tab — no browser storage involved. +- The user's daemon token and third-party credentials never reach the browser. + ## Flows - A single JSON file per machine in the user's home, owned by the daemon, so the dashboard never needs browser storage. - Reads are forgiving — a missing, malformed, or old-shaped file yields an empty registry — and every value is sanitized on the way in (clamped numbers, known-set strings, capped lists), so a hand-edited or hostile file can neither break anything nor smuggle junk into an agent. - One shape, and one spelling per setting. A file in an older shape reads as no projects and no preferences at all, and a setting under an older name is simply not there — nothing is translated on the way in, so a file left behind by a rename is brought up to date by hand. -- How far a finished agent publishes itself is stored as the single rung it is. +- How far a finished agent publishes itself — keep it local, push, open a PR, or merge — is stored as one position on that ladder. - One tier of preferences lives here, and it is the user's; repo-shaped settings belong in the repo's committed file. - Patch writes touch only the keys the caller changed, so a stale dashboard tab cannot silently revert someone else's setting. - Writes are atomic, serialized, and owner-only readable. diff --git a/packages/the-framework/src/stale-branch.SPEC.md b/packages/the-framework/src/stale-branch.SPEC.md index 567e3f4fb..eee81d025 100644 --- a/packages/the-framework/src/stale-branch.SPEC.md +++ b/packages/the-framework/src/stale-branch.SPEC.md @@ -1,5 +1,9 @@ Releases a pinned routine branch its closed or merged PR left behind, so a recurring job stops reporting a pending agent that no longer exists. +## User Stories + +- The user's recurring routine resumes on schedule after its PR merges or closes, instead of standing down forever behind a leftover branch. + ## Flows - Routine agents pin their branch name so two firings never run at once; a firing that finds the branch already existing stands down. diff --git a/packages/the-framework/src/steps.SPEC.md b/packages/the-framework/src/steps.SPEC.md index 257df32e0..c8da16023 100644 --- a/packages/the-framework/src/steps.SPEC.md +++ b/packages/the-framework/src/steps.SPEC.md @@ -1,5 +1,10 @@ The prompts a build agent opens with, and the one check that decides between them. +## User Stories + +- The user points an agent at an existing project and it works within that codebase — it never re-scaffolds. +- The user starts in an empty workspace and the agent scaffolds the whole app from scratch. + ## Flows - The framing states the one thing the agent cannot infer: build from scratch in an empty workspace, or work within the codebase that already exists — an existing project is extended, never re-scaffolded. diff --git a/packages/the-framework/src/system-prompt.SPEC.md b/packages/the-framework/src/system-prompt.SPEC.md index 75fef756f..195839030 100644 --- a/packages/the-framework/src/system-prompt.SPEC.md +++ b/packages/the-framework/src/system-prompt.SPEC.md @@ -1,5 +1,11 @@ Composes, in one place, the entire system channel every agent runs under, so every surface agrees on exactly what it was told. +## User Stories + +- The user reads the exact system prompt their agent ran under — one place composes it, so what is shown is what ran. +- The user's own instructions (`SYSTEM.md`) ride on top of the built-in prompt. +- The user drops the built-in prompt with vanilla mode, or everything framework-authored with transparent mode. + ## Flows - Fixed order: the project context (goal, knowledge docs, tickets, queue — with the ticket and queue format specs inlined into the channel itself), then the built-in prompt, then the user's own instructions, then the emit protocols, signal protocol always last. diff --git a/packages/the-framework/src/terminal.SPEC.md b/packages/the-framework/src/terminal.SPEC.md index 294a62abc..72be849da 100644 --- a/packages/the-framework/src/terminal.SPEC.md +++ b/packages/the-framework/src/terminal.SPEC.md @@ -1,5 +1,10 @@ Renders the agent's event stream as one human-readable terminal line per event — the CLI's counterpart to the dashboard's views over the same stream. +## User Stories + +- The user follows an agent from the terminal, one line per event, and is never left inferring an outcome. +- The user reads a refusal as its reason, so a setting never looks like a bug. + ## Flows - Consequences over flags: the handoff line says what will happen ("push the branch, open a PR, and merge it"), and every merge outcome is spoken — after "auto-merge is on", silence would read as "it merged". diff --git a/packages/the-framework/src/ticket-locks.SPEC.md b/packages/the-framework/src/ticket-locks.SPEC.md index 05bc4397c..f9361606a 100644 --- a/packages/the-framework/src/ticket-locks.SPEC.md +++ b/packages/the-framework/src/ticket-locks.SPEC.md @@ -1,9 +1,14 @@ Claims a ticket for a single agent by committing a lock file beside it on the data branch, so agents on other machines and in the cloud cannot double-work the same ticket. +## User Stories + +- The user runs agents on several machines and in the cloud, and no ticket is ever double-worked. +- The user releases a stuck claim by hand; the daemon frees on its own only a claim whose agent ended with nothing to hand off. + ## Flows - A claim is a lock sibling in the tickets folder naming the holding agent — the place every agent already looks, and a file the stock prompts already skip. -- A lock is one more data-branch write: the shared write funnel syncs, commits the batch, and pushes the branch, so a claim reaches every machine the way all framework data does — and a lost push race re-judges the batch against what actually landed instead of overwriting anyone's claim. +- A lock is one more data-branch write: the shared write funnel syncs, commits the batch, and pushes the branch, so a claim reaches every machine the way all framework data does. A lost push race re-judges the batch against what actually landed, instead of overwriting anyone's claim. - A batch says which side of the ticket's life it claims for: a planning batch skips a ticket that already has a plan (the work it came for is done), while an implementing batch reads the plan as its input and is stopped only by an existing lock. - The claim is the committed state: a batch whose cycle failed whole claimed nothing, while one that committed but could not push still guards local agents and says so out loud. - There is no timed expiry. The lock lifts when the agent retires it on the data branch with its finished work, when a human releases it, or when the daemon frees a claim it made for an agent that ended with nothing to hand off. The daemon only ever frees a lock still naming the exact agent it made it for. diff --git a/packages/the-framework/src/tickets.SPEC.md b/packages/the-framework/src/tickets.SPEC.md index 24ae49e83..12d079437 100644 --- a/packages/the-framework/src/tickets.SPEC.md +++ b/packages/the-framework/src/tickets.SPEC.md @@ -8,9 +8,9 @@ The repo's ticket and queue conventions: tickets are plain markdown files in a r ## Flows -- A ticket put on the queue keeps its identity as a markdown link back to the ticket file; only a link to a plain file directly inside the tickets folder counts — traversal, absolute paths, URLs, and nesting are all refused. -- A ticket's written priority (0-10) maps straight onto the queue's numbered sections; anything else — words, out-of-range, fractions — lands in the middle rather than being guessed at. -- A ticket's GitHub header names the issue it tracks, which is what lets merging the work close that issue. +- When the user queues a ticket, the entry keeps the ticket's identity as a markdown link back to its file; only a link to a plain file directly inside the tickets folder counts — traversal, absolute paths, URLs, and nesting are all refused. +- The priority the user wrote on the ticket (0-10) maps straight onto the queue's numbered sections; anything else — words, out-of-range, fractions — lands in the middle rather than being guessed at. +- The GitHub header the user put on a ticket names the issue it tracks, which is what lets merging the work close that issue. ## Rationales diff --git a/packages/the-framework/src/todo-loop.SPEC.md b/packages/the-framework/src/todo-loop.SPEC.md index a4bc2a690..66cf4965b 100644 --- a/packages/the-framework/src/todo-loop.SPEC.md +++ b/packages/the-framework/src/todo-loop.SPEC.md @@ -8,10 +8,12 @@ Drains the project's task queue one entry per turn until it is empty, and owns t ## Flows -- The framework drives: read the queue's first open entry fresh off the data branch, gate ("start the next item?" — an agent nobody is watching takes the recommended answer and carries on), prompt the agent to complete exactly that entry, then check it off on the data branch itself — the queue is not the agent's file to edit, and an entry someone else already retired is simply found done. +- The framework, not the agent, drives the drain: each turn it reads the queue's first open entry fresh off the data branch, asks "start the next item?", prompts the agent to complete exactly that entry, then checks it off on the data branch itself. +- The user watching answers that per-item gate and can stop the loop there; an agent nobody is watching takes the recommended answer and carries on. +- The queue is not the agent's file to edit — the framework writes every check-off, and an entry someone else already retired is simply found done. - Safe to leave unattended: a hard item cap, the agent's stop and budget signal, and a write guard — a check-off that cannot land stops the loop rather than re-serving finished work. - A backlog turn is a full turn: ask-gates and signals are honored there too, with ready-for-merge fired once across the whole backlog. -- An entry queued with a priority lands in its numbered section, not at the end of the file. Every queue write, the paused agent's resume note included, goes through the data branch's write funnel. +- An entry the user queues with a priority lands in its numbered section, not at the end of the file. Every queue write, the paused agent's resume note included, goes through the data branch's write funnel. - An agent's own session TODO file (a checkout file, not the queue) with open entries withholds auto-merge — a temporary belt under the agent's own ready signal. ## Rationales diff --git a/packages/the-framework/src/turn-gate.SPEC.md b/packages/the-framework/src/turn-gate.SPEC.md index 6c6b89c59..2442edc99 100644 --- a/packages/the-framework/src/turn-gate.SPEC.md +++ b/packages/the-framework/src/turn-gate.SPEC.md @@ -1,5 +1,10 @@ The turn-boundary contract with the wrapped agent: each turn runs as a black box, so everything the framework learns — the agent stopping to ask, views to show, its chosen session name, ready-for-merge — is a tagged block parsed out of the turn's final message. +## User Stories + +- The user answers the agent's question, and the agent resumes the same conversation. +- The user's answer can end the agent instead of resuming it — declining a plan stops the run. + ## Flows - The protocol texts appended to the system channel pin how to emit, not when: one blocking ask-gate and the non-blocking signals (markdown views, session name, ready-for-merge). From cef878fc2a5633c8f03ae9019bff82657606fbe8 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 09:02:22 +0000 Subject: [PATCH 07/16] Style pass: re-voice src agent, autonomy, and CI-watch specs to the user-story axis User Stories on the agent lifecycle (watch, answer, decline, chat, resume, exact system prompt) and CI watch (merge on green anywhere, fix agent on red); "tick" and "holder" jargon replaced; monster bullets split; effects stated first. Four stale quota/budget claims found against the code are preserved and flagged for the follow-up, per the pass's rules. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/src/agent-locks.SPEC.md | 2 +- .../the-framework/src/agent-telemetry.SPEC.md | 5 +++-- packages/the-framework/src/agent.SPEC.md | 14 ++++++++++++-- packages/the-framework/src/agent.test.SPEC.md | 2 +- .../src/archived-agent-patch.SPEC.md | 4 ++-- packages/the-framework/src/auto-pm.SPEC.md | 16 ++++++++-------- packages/the-framework/src/auto-pm.test.SPEC.md | 2 +- packages/the-framework/src/branch-links.SPEC.md | 6 +++++- packages/the-framework/src/ci-watch.SPEC.md | 5 +++++ packages/the-framework/src/claude-trust.SPEC.md | 4 ++++ packages/the-framework/src/cli.SPEC.md | 4 ++-- packages/the-framework/src/cloud-work.SPEC.md | 2 +- .../the-framework/src/daemon-runtime.SPEC.md | 4 ++-- .../the-framework/src/daemon-services.SPEC.md | 2 +- 14 files changed, 48 insertions(+), 24 deletions(-) diff --git a/packages/the-framework/src/agent-locks.SPEC.md b/packages/the-framework/src/agent-locks.SPEC.md index 060fe4df7..b9da81bc1 100644 --- a/packages/the-framework/src/agent-locks.SPEC.md +++ b/packages/the-framework/src/agent-locks.SPEC.md @@ -2,7 +2,7 @@ Serializes everything that touches one agent's checkout, so a finishing agent's ## Flows -- A failed holder surfaces its own error without blocking whoever waits behind it. +- An action that fails while holding the lock reports its own error; the action waiting behind it still runs. ## Rationales diff --git a/packages/the-framework/src/agent-telemetry.SPEC.md b/packages/the-framework/src/agent-telemetry.SPEC.md index 829168bab..b7c1746cc 100644 --- a/packages/the-framework/src/agent-telemetry.SPEC.md +++ b/packages/the-framework/src/agent-telemetry.SPEC.md @@ -2,8 +2,9 @@ The accounting every agent shares, whatever kind it is: naming the session, foll ## Flows -- The agent's real session id is surfaced the moment a turn starts, not only when it ends, so the handle for resuming the conversation survives a stop or crash mid-turn. -- One self-stop: an answer that says to stop. The caller's signal and that one are composed, so everything downstream ends the same way whichever fired. +- The handle for resuming the conversation survives a stop or crash mid-turn: the agent's real session id is surfaced the moment a turn starts, not only when it ends. +- Each turn's spend is folded into a running total as the turn reports, so the dashboard's per-agent spend readout is live rather than final. +- One self-stop exists: an answer that says to stop. It is composed with the stop signal from outside (the Stop button, Ctrl+C), so everything downstream ends the same way whichever fired. - An unreadable quota never stops the work: a failing quota check means carry on. - One shared classification of how an agent ended — a user stop, a quota pause (which leaves a note to resume from), or a real failure — so every surface agrees on what "stopped" means. diff --git a/packages/the-framework/src/agent.SPEC.md b/packages/the-framework/src/agent.SPEC.md index 2482e47b9..f4cfef75e 100644 --- a/packages/the-framework/src/agent.SPEC.md +++ b/packages/the-framework/src/agent.SPEC.md @@ -1,17 +1,27 @@ One agent: frame it, send it one prompt, honor the gates it answers with, work the backlog, and stay open for the user's own messages — every step streamed as events. +## User Stories + +- The user watches every step of a live agent as an event stream the dashboard renders as a transcript. +- The user answers the agent's question and the answer continues the same conversation; with nobody at the keyboard, the recommended option is taken instead. +- The user declines the agent's plan and the agent stops cleanly instead of building on it. +- The user chats with a live agent, each message continuing the same conversation. +- The user resumes a stopped agent and it picks up the same conversation. +- The user reads the exact system prompt the agent ran under, with nothing appended to it behind their back. + ## Flows - Frame the agent → opening prompt (pausing on its questions) → scaffold retry, if a build produced nothing → backlog loop → live chat → end. - A build and a verbatim prompt are one path: an opening prompt honoring gates. The whole difference is two options: which prompt opens the agent, and whether its own backlog is worked afterwards. - Nothing about the project reaches the agent's prompt: the system framing shown on the dashboard is exactly and entirely what the agent runs under. - Nothing reviews the work: the agent is a black box, and its turn is the whole of it. -- A turn that stops to ask becomes a live question, and the answer continues the same conversation — bounded, so an agent that keeps asking cannot loop forever. With nobody to ask, the recommended option is taken and the agent carries on, which is what an unattended one is for. +- When a turn stops to ask, the user sees a live question, and the answer continues the same conversation — bounded, so an agent that keeps asking cannot loop forever. With nobody to ask, the recommended option is taken and the agent carries on, which is what an unattended one is for. - An answer the agent marked as ending it does exactly that, and cleanly: a declined plan reads as a stop rather than a failure, and the agent is never resumed with it. It stops through the same signal a Stop does, so a decline cannot read as a finished agent on one path and a stop on another. +- Once the opening exchange settles, the user's own chat messages each continue the same conversation. An agent whose chat queue goes idle ends itself — unless its own surface is the only one there is, with no dashboard to resume through, in which case it stays parked for the next message. - The budget and quota stops hold even when nothing runs after the opening turn. - A build whose opening turn leaves the workspace empty means the agent stalled, so it is re-prompted once with a hard "create it from scratch" directive. - An agent whose *location* is a cloud session ends at the hand-off, because every later phase would misread the hand-off note as the agent's own reply. Where an agent runs is its own axis, separate from which coding-agent CLI drives it. -- Resuming a stopped agent continues the same conversation with the message sent verbatim — the old transcript already carries the framing — while the surrounding flow still runs. +- When the user resumes a stopped agent, the message is sent verbatim into the same conversation — the old transcript already carries the framing — while the surrounding flow still runs. - On any stop or failure: classify why, emit a clean end event, and release the driver session. ## Rationales diff --git a/packages/the-framework/src/agent.test.SPEC.md b/packages/the-framework/src/agent.test.SPEC.md index 5e2304920..d8b418eae 100644 --- a/packages/the-framework/src/agent.test.SPEC.md +++ b/packages/the-framework/src/agent.test.SPEC.md @@ -1,4 +1,4 @@ -Tests the whole agent flow offline: the agent's question gates — one pick, several at once, and an approval, which is that one gate with two options — with the auto-accept fallback when nobody is there to ask, and an answer the agent marked as ending it doing exactly that, while the same label unmarked is an ordinary answer, budget and quota stops with resume notes, session links and usage totals, backlog and chat phases, greenfield vs. existing-codebase framing and the once-only scaffold retry, a prompt agent running its text unframed and working no backlog, hand-off agents ending at the hand-off, and resuming a stopped agent verbatim. +Tests the whole agent flow offline: the agent's question gates — one pick, several at once, and plan approval, which is that same gate with two options — the auto-accept fallback when nobody is there to ask, an answer the agent marked as ending it doing exactly that while the same label unmarked stays an ordinary answer, budget and quota stops with resume notes, session links and usage totals, the backlog and chat phases, greenfield versus existing-codebase framing and the once-only scaffold retry, a prompt agent running its text unframed and working no backlog, hand-off agents ending at the hand-off, and resuming a stopped agent verbatim. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/archived-agent-patch.SPEC.md b/packages/the-framework/src/archived-agent-patch.SPEC.md index 0ff52ba86..db7b7b14b 100644 --- a/packages/the-framework/src/archived-agent-patch.SPEC.md +++ b/packages/the-framework/src/archived-agent-patch.SPEC.md @@ -1,4 +1,4 @@ -Records a fact learned about a run after it ended — the pull request its work is on, the branch a cloud session landed it on — onto the run's archived record, as a commit on the data branch. +Records a fact learned about a run after it ended — the pull request its work is on, the branch a cloud session landed it on — onto the run's archived record, as a commit on the data branch (`tf-data`, the shared branch where agent records are archived). ## Flows @@ -6,7 +6,7 @@ Records a fact learned about a run after it ended — the pull request its work ## Rationales -- The write funnel is the only durable path: written straight into the checkout, the fact would be wiped within a minute, since the sync hard-resets a dirty checkout. +- The write funnel is the only durable path: written straight into the data branch's checkout, the fact would be wiped within a minute, since the sync hard-resets a dirty checkout. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/auto-pm.SPEC.md b/packages/the-framework/src/auto-pm.SPEC.md index e876af472..79e946398 100644 --- a/packages/the-framework/src/auto-pm.SPEC.md +++ b/packages/the-framework/src/auto-pm.SPEC.md @@ -5,24 +5,24 @@ Auto PM spends leftover subscription quota on the product's own roadmap: while t - The user leaves the keyboard and comes back to a worked-down queue and freshly imported, triaged, and planned tickets. - The user caps how many unattended agents a project runs at once and switches individual routines off. - The user clicks "Run now" on a routine and it runs, even while the master switch is off. -- The user reads the reason for every tick that started nothing, so a setting never looks like a bug. +- The user reads the reason for every sweep pass that started nothing, so a setting never looks like a bug. ## Flows -- One pure policy question per project — enabled, under the concurrency cap, past the cooldown, queue readable, quota under the boundary; the sweep loop only supplies the readings. +- Whether a pass starts anything is one pure policy question per project — enabled, under the concurrency cap, past the cooldown, queue readable, quota under the boundary; the sweep loop only supplies the readings. - A standing queue is drained before new work is invented; a calendar-paced codebase maintenance sweep outranks the rotation when due, and only ever while the queue is genuinely empty. -- Draining and planning fan out, one pinned queue entry or locked ticket per agent, so concurrent agents do disjoint work; every other routine stays one per tick since concurrent copies would undo each other. -- Both phases claim their ticket with a pushed lock file before the agent starts, so agents on other machines cannot double-book it: planning locks the ticket it will plan, and draining locks the ticket its queue entry links back to — an entry claimed elsewhere is dropped from the batch, and an entry with no ticket behind it keeps the queue itself as the coordination point. -- A claim whose agent settled with nothing to hand off is released by the sweep: the pull request that normally lifts the lock is never coming, and without the release the queue would jam forever on a dead claim. The freed work is not respawned by this daemon — one commitless run is evidence for a human, not an invitation to repeat it every cooldown — and a claim whose agent never even started (a refused spawn, a stop mid-batch) is freed the same way. +- Draining and planning fan out, one pinned queue entry or locked ticket per agent, so concurrent agents do disjoint work; every other routine stays one per pass since concurrent copies would undo each other. +- Both phases claim their ticket with a pushed lock file before the agent starts, so agents on other machines cannot double-book it: planning locks the ticket it will plan, and draining locks the ticket its queue entry links back to. An entry claimed elsewhere is dropped from the batch, and an entry with no ticket behind it keeps the queue itself as the coordination point. +- A claim whose agent settled with nothing to hand off is released by the sweep: the pull request that normally lifts the lock is never coming, and without the release the queue would jam forever on a dead claim. The freed work is not respawned by this daemon — one commitless run is evidence for a human, not an invitation to repeat it every cooldown. A claim whose agent never even started (a refused spawn, a stop mid-batch) is freed the same way. - The queue coordinates a ticketless entry only once its check-off is in the checkout, and that leaves a window: an agent handed off to a cloud session settles locally before its pull request lands, so until the merge reaches the checkout the entry still reads open, and past the cooldown it can be fanned out to a second agent. The same window opens when the daemon restarts, since only its in-memory pin covered the wait. - Each routine can be switched off individually, and every stand-down is reported with its reason: a wedged sweep must not look like a healthy idle one. -- Switching the draining routine off means "do not *work* the queue", not "do nothing": the tick falls through to the rotation, which puts entries *on* the queue rather than taking them off. The one exception is a click that asked for the queue by name: a drain-only sweep says why it cannot, rather than borrowing the click. +- Switching the draining routine off means "do not *work* the queue", not "do nothing": the pass falls through to the rotation, which puts entries *on* the queue rather than taking them off. The one exception is a click that asked for the queue by name: a drain-only sweep says why it cannot, rather than borrowing the click. ## Rationales -- An unreadable quota fails closed — the opposite of the per-agent guard: quietly burning quota on work nobody asked for is worse than skipping a tick. +- An unreadable quota fails closed — the opposite of the per-agent guard: quietly burning quota on work nobody asked for is worse than skipping a pass. - "Run now" skips only the master switch: the click is the consent the preference exists to record; every other stand-down holds. -- A switched-off draining routine falls through to the rotation rather than standing the tick down, because a stand-down would make every inventing routine unreachable whenever the queue holds anything — and the queue is auto-populated, so it usually does. +- A switched-off draining routine falls through to the rotation rather than standing the pass down, because a stand-down would make every inventing routine unreachable whenever the queue holds anything — and the queue is auto-populated, so it usually does. - The ticketless hand-off window is accepted rather than closed: closing it would take a durable per-entry claim — a second claim shape beside the pushed ticket lock that already covers the queue's normal case — and the queue's planned move onto an eagerly-pushed data branch closes the window structurally, so a claim shape built now would be deleted then. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/auto-pm.test.SPEC.md b/packages/the-framework/src/auto-pm.test.SPEC.md index 8e245d646..7bcb11e17 100644 --- a/packages/the-framework/src/auto-pm.test.SPEC.md +++ b/packages/the-framework/src/auto-pm.test.SPEC.md @@ -1,4 +1,4 @@ -Covers auto PM's decision policy (quota and queue both failing closed when unreadable, cooldowns, concurrency caps), the drain-before-refill cycle, rotation order and per-routine opt-outs (an unticked drain routine falling through to the rotation, while a drain-only click still stands down), the calendar-paced maintenance sweep, fan-out with pinned entries and locked tickets, drains claiming their entry's ticket before starting (ticketless entries exempt, a lost claim dropping the entry rather than the batch), durable claims surviving restarts and hand-offs, a claim released when its agent settles with nothing to hand off and only then (the freed work not respawned, refused-spawn claims freed too, failed releases retried under a bound), on-demand and drain-only ticks, and the report the dashboard shows. +Covers auto PM's decision policy — quota and queue both failing closed when unreadable, cooldowns, concurrency caps — the drain-before-refill cycle, rotation order and per-routine opt-outs (an unticked drain routine falls through to the rotation; a drain-only click still stands down), the calendar-paced maintenance sweep, fan-out with pinned entries and locked tickets, drains claiming their entry's ticket before starting (ticketless entries exempt; a lost claim drops the entry, not the batch), durable claims surviving restarts and hand-offs, a claim released when its agent settles with nothing to hand off and only then (the freed work not respawned; refused-spawn claims freed too; failed releases retried under a bound), on-demand and drain-only passes, and the report the dashboard shows. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/branch-links.SPEC.md b/packages/the-framework/src/branch-links.SPEC.md index ad8ea135a..2bd1bca3b 100644 --- a/packages/the-framework/src/branch-links.SPEC.md +++ b/packages/the-framework/src/branch-links.SPEC.md @@ -1,4 +1,8 @@ -Keeps every session checkout reachable by its branch name: new checkouts live in `.the-framework/branches/` in a folder named as their branch, a symlink appears beside them whenever a branch gets renamed, and a `branches` shortcut at the repo root points there — so `cd branches/` opens any session's work by the name the dashboard shows. +Keeps every session checkout reachable by its branch name: new checkouts live in `.the-framework/branches/` in a folder named as their branch, a symlink appears beside them whenever a branch gets renamed, and a `branches` shortcut at the repo root points there. + +## User Stories + +- The user opens any session's checkout from the repo root — `cd branches/` — by the branch name the dashboard shows. ## Flows diff --git a/packages/the-framework/src/ci-watch.SPEC.md b/packages/the-framework/src/ci-watch.SPEC.md index 5c39b1265..9b7a250fe 100644 --- a/packages/the-framework/src/ci-watch.SPEC.md +++ b/packages/the-framework/src/ci-watch.SPEC.md @@ -1,5 +1,10 @@ Watches the pull requests the framework is waiting to land and acts on what their checks say: merge the green ones, start an unattended fix agent for the red ones. +## User Stories + +- The user arms merge-on-green and the pull request merges by itself once its checks pass — in any repo, with or without GitHub's native auto-merge. +- The user finds a fix agent already at work when an agent's pull request goes red; after two attempts the failure is left to them. + ## Flows - Merge-on-green everywhere: the sweep polls a watched PR's checks about once a minute and merges once they pass. diff --git a/packages/the-framework/src/claude-trust.SPEC.md b/packages/the-framework/src/claude-trust.SPEC.md index 92c4d72fb..e40a50335 100644 --- a/packages/the-framework/src/claude-trust.SPEC.md +++ b/packages/the-framework/src/claude-trust.SPEC.md @@ -1,5 +1,9 @@ Reads and grants Claude Code's own folder trust, so a web run never dies on the CLI's interactive trust question. +## User Stories + +- The user starts a web agent and never answers Claude Code's folder-trust question by hand. + ## Flows - The trust record written is the CLI's own — the same one the user's accepting of the dialog would leave behind — and everything else in the CLI's config survives the write. diff --git a/packages/the-framework/src/cli.SPEC.md b/packages/the-framework/src/cli.SPEC.md index 920d40b65..a24ce5e35 100644 --- a/packages/the-framework/src/cli.SPEC.md +++ b/packages/the-framework/src/cli.SPEC.md @@ -9,11 +9,11 @@ The framework command: serves the dashboard in the foreground, and runs one agen - Four options and no verbs: `--host` and `--port`, plus `--help` and `--version`. Everything else is the dashboard, which is the product's user interface. - One more, not for humans: `--agent ` runs the agent described by a JSON spec. That is how the dashboard spawns one. -- A live agent gets everything around the driver: settings resolved across config layers, persisted events, the steering channel, the browser, and live chat. It serves no dashboard of its own — the one that spawned it reads its event log off disk and steers it through the control file. +- A live agent gets everything around the driver: settings resolved across config layers, persisted events, the steering channel, the browser, and live chat. It serves no dashboard of its own — the one that spawned it reads its event log off disk and steers it through the steering channel's file. - Three shapes share that wiring: the full build flow, a verbatim prompt (research included), and transparent mode — the driver completely raw. - Every agent settles identically: the quality follow-up if asked for and earned, then the handoff — commit what it left, push the branch, open a draft PR, merge only when authorized — then a project-log entry, written even for agents that stopped or crashed. - Settings that cannot apply say so before the spending; a stopped agent never publishes. -- A build whose bookkeeping layout differs from the one the repo records is refused before it writes anything, with both layouts and the fix named. +- A build whose bookkeeping layout — the on-disk names its records hang off — differs from the layout the repo records is refused before it writes anything, with both layouts and the fix named. - Once an agent starts it is never interrupted for quota. The gate is on starting, and it lives with the daemon that decides whether to start unattended work at all. - Ctrl+C aborts the agent itself, the driver's process tree included; a second press force-quits. diff --git a/packages/the-framework/src/cloud-work.SPEC.md b/packages/the-framework/src/cloud-work.SPEC.md index ecee8124f..6581a4ee7 100644 --- a/packages/the-framework/src/cloud-work.SPEC.md +++ b/packages/the-framework/src/cloud-work.SPEC.md @@ -10,7 +10,7 @@ Adopts the branch a cloud session actually worked on: each settled web run is ma - A web run hands the task to claude.ai and ends; the cloud session does the work on a branch of its own naming, never the branch the run was born on. - The match is exact, never guessed: the hand-off pushed a commit unique to the run for the session to clone at, so the session's branch — and only it — descends from that commit. A run matching no branch (the session has not pushed, or never will) or more than one is simply asked again next pass, and a run past the window (two days) stops being asked about. - What gets recorded, as one commit on the data branch (the dedicated branch the framework's own records live on) so every machine learns it: the branch, and the pull request the session opened for it. A run that was armed for a PR the session never opened gets its draft PR opened by the daemon — unless the branch carries nothing beyond the hand-off itself, or the session's pull requests could not be listed that pass. -- A later daemon pass patches the run's record, the same way a late-opened PR already is. +- Adoption is not one-shot: a run recorded while its armed pull request was still missing is asked about again, and a later pass patches the pull request on once it exists. - Adoptions and failures are said out loud; a run still waiting is not. ## Rationales diff --git a/packages/the-framework/src/daemon-runtime.SPEC.md b/packages/the-framework/src/daemon-runtime.SPEC.md index 355034a76..c8f25ff70 100644 --- a/packages/the-framework/src/daemon-runtime.SPEC.md +++ b/packages/the-framework/src/daemon-runtime.SPEC.md @@ -13,10 +13,10 @@ What the daemon does for a project: starting agents in isolated checkouts, retir - Each agent gets its own worktree and branch, so concurrent agents never touch each other or the user's checkout; a project that is not a Git repo falls back to its main checkout, one agent at a time. A repo whose worktree cannot be created fails the start rather than borrowing the user's working tree. - A start is refused when the chosen driver cannot run (not installed or not logged in), so a doomed agent spends no branch or worktree. - The agents the daemon is still responsible for — spawning, running, or mid-retirement — are named for the background sweep, so it never reclaims a checkout out from under a teardown. -- A finished agent's history is archived onto the data branch — committed and pushed the moment the session settles — and its checkout is reclaimed once the work reaches the remote: the one retention rule, applied whatever the agent did. A push that cannot land keeps the checkout, and the background sweep retries it later. +- A finished agent's history is archived onto the data branch (the dedicated branch the framework's shared records live on) — committed and pushed the moment the session settles — and its checkout is reclaimed once the work reaches the remote: the one retention rule, applied whatever the agent did. A push that cannot land keeps the checkout, and the background sweep retries it later. - An agent killed by a transient connection error is continued automatically (at most twice), and a child that died before booting is marked failed with the cause surfaced — never left "waiting to start" forever. - On shutdown, live agents are stopped rather than orphaned; each keeps its worktree and branch, so the dashboard can continue the same conversation in the same checkout when asked. A start that lands while the daemon is shutting down is refused rather than spawned into the gap between the stop pass and the server closing, where nothing would ever stop it. The refusal takes back the fresh worktree and branch it had already allocated, which no agent ever owned. An agent can also be forwarded to a connected device, its events relayed back. -- Stopping resolves when the daemon has let go of the repo, not when the processes die: a child's exit event lands after its pid disappears, and the teardown that event starts — archive the agent, commit its work, keep or remove its checkout — runs well past that. A teardown that wedges costs the shutdown its grace period, not the exit. +- Stopping resolves when the daemon has let go of the repo, not when the processes die: a child's exit event lands after its pid disappears, and the teardown that event starts — archive the agent, commit its work, keep or remove its checkout — runs well past that. A teardown that wedges costs the shutdown at most its bounded grace period — it never blocks the exit for good. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/daemon-services.SPEC.md b/packages/the-framework/src/daemon-services.SPEC.md index 80b9ee5df..eb44b4b69 100644 --- a/packages/the-framework/src/daemon-services.SPEC.md +++ b/packages/the-framework/src/daemon-services.SPEC.md @@ -12,7 +12,7 @@ Everything the daemon runs in the background beside serving the dashboard: Disco - One clock runs every background job, each declaring how many ticks it wants between turns rather than owning an interval. - Every service re-reads its preference on each tick, so a dashboard toggle takes effect without restarting the daemon. -- An agent the daemon starts resolves its options from the same two tiers the launcher uses — your settings, then the repo's committed file — so one nobody asked for and one someone clicked differ only in who asked. +- An agent the daemon starts resolves its options from the same two tiers a dashboard start uses — the user's settings, then the repo's committed file — so an agent nobody asked for and one someone clicked differ only in who asked. - Auto PM spends idle quota on the roadmap: it fans out up to the configured number of unattended agents, each pinned to one queue entry. An entry is retired on the data branch (the dedicated branch the framework's shared records live on) once its agent's ending reports the work published — and it is the daemon, never the agent, that writes queue check-offs and ticket locks. - The CI watch merges a watched PR once its checks pass, and puts a fix agent on one whose checks fail. - An hourly sweep deletes the dead refs Claude-web hand-offs leave on origin, once they are old enough and provably hold no work. From 31bfa005ae4c7ccea97797e6b09a40833f53598f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 09:07:46 +0000 Subject: [PATCH 08/16] Style pass: re-voice the flagship package spec to the user-story axis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Twelve user stories in plain sentences, each traceable to FEATURES-SPEC.md; the Flows — TL;DR rebuilt one sentence per flow (14 for 14); every flow paragraph opens from the user's side; glossary grows agent, composer, routine, handoff, and quota week; the control file glossed inline. Twelve stale claims found against the code (mostly #1582 data-branch drift) are preserved and flagged for the follow-up round. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/SPEC.md | 56 ++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/packages/the-framework/SPEC.md b/packages/the-framework/SPEC.md index f8c1a97af..50ebf31a2 100644 --- a/packages/the-framework/SPEC.md +++ b/packages/the-framework/SPEC.md @@ -6,7 +6,9 @@ The product: turnkey AI orchestration that wraps a coding-agent CLI (Claude Code - The user activates a repo from the dashboard and it becomes a registered project, with an onboarding checklist derived from real facts rather than clicks. - The user starts an agent by typing a prompt (attended) or picking a preset (unattended) — from the composer, a ticket row, a queue entry, a routine, or the CLI. - The user watches a live transcript, answers the agent's questions inline where they happened, chats with it, stops and resumes it, and reopens a finished agent as the same conversation. +- The user reads the exact system prompt an agent ran under, layers their own instructions on top of the built-in one, and saves custom presets privately or into the repo for the team. - The user receives finished work as a pull request, and can arm auto-merge so it lands once the agent signals ready and checks are green. +- The user removes an agent's checkout — or lets the daemon reclaim idle ones — and can always recover the work from the agent's branch. - The user browses a cross-project ticket list, filters it, shares the filtered view as a URL, and queues or starts work from a ticket. - The user walks away and the daemon keeps working the roadmap: draining the confirmed queue, refilling it by triaging and planning tickets, fixing red CI on its own PRs, and merging on green. - The user sets no budget: unattended work stands down past the elapsed share of the quota week, and work the user asked for is never starved. @@ -15,11 +17,20 @@ The product: turnkey AI orchestration that wraps a coding-agent CLI (Claude Code ## Flows — TL;DR -- One daemon per machine. Running the CLI in any registered repo finds it; it serves the dashboard, spawns agents, and runs the background services (autonomy sweeps, notifications, chat, CI watch). -- An agent is one task worked in its own git worktree on its own branch. It streams what it does as events; the human can watch, answer its questions, and chat with it live — or not be there at all. -- When an agent ends with real work, the work is pushed and opened as a PR. Empty agents publish nothing. -- When nobody is around, the daemon plays product manager: it drains the confirmed-task queue, refills it by triaging and planning tickets, keeps CI green on the PRs it opened, and merges them once checks pass — all bounded by the account's own quota week. -- Which CLI drives the work is a swappable axis (the *driver*), and where it executes is another (the *location*); the CLI keeps its own subscription auth, and The Framework never runs its own model calls for the coding work. +- Activating a repo commits its dirty state, seeds the framework's own files, and registers the project; a preflight refuses a doomed agent before anything is spent. +- The user starts an agent — composer, ticket row, queue entry, routine, onboarding, or CLI — and the daemon spawns it detached, relaying it when aimed at a saved remote device. +- Every agent works in its own git worktree on its own branch, so the user's checkout is never touched. +- The driver runs each turn as a black box and everything learned from a turn is parsed from its final message; which CLI drives and where it executes are two swappable axes. +- A question parks the agent as an answerable card, chat continues the same conversation, and unattended agents take the recommended answer. +- After its main task, the agent drains its queue file one gated entry per turn. +- A finished agent's work is committed, pushed, and opened as a pull request — unless the agent is empty, which publishes nothing. +- Removal follows one rule — the checkout goes only once the remote has the work — so every deletion is recoverable. +- While nobody is around, the daemon drains the confirmed queue, refills it by triaging and planning tickets, and phrases every refusal as a reason. +- Configuration arms auto-merge, the agent's own ready signal authorizes it, and the CI watch merges green PRs and puts one fix agent on red ones. +- Unattended work stands down past the elapsed share of the account's quota week; work the user asked for is never starved. +- One daemon per machine serves the dashboard from the files agents write, and agents can also run on a saved device, GitHub Actions, or a Claude cloud session — or drive a real browser the user can take over. +- What must outlive a process — agent history, tickets, the queue, the project log — lands in git. +- One path composes every agent's recorded system prompt, presets are one catalog, and two switches (vanilla, transparent) dial the wrapping down. ## Flows @@ -37,33 +48,33 @@ flowchart TD Handoff --> Teardown["Teardown & retention"] ``` -**Setup.** Activating a repo commits any dirty state first (so the activation commit is clean), creates the `.the-framework/` state directory, seeds the project log, teaches `.gitignore` which parts stay untracked, and registers the project in a per-machine registry in the user's home — which also holds the user's dashboard preferences (project settings override user settings, only for keys a project may override) and the daemon's secrets. Before an agent spawns anything, a preflight probes the driver CLI it picked, so a missing prerequisite fails early and clearly; a guard the picked driver cannot honor is announced rather than silently ignored. +**Setup.** When the user activates a repo, any dirty state is committed first — so the activation commit is clean — then the `.the-framework/` state directory is created, the project log is seeded, and `.gitignore` is taught which parts stay untracked. The project is registered in a per-machine registry in the user's home, which also holds the user's dashboard preferences (project settings override user settings, only for keys a project may override) and the daemon's secrets. Before an agent spawns anything, a preflight probes the driver CLI it picked, so a missing prerequisite fails early and clearly; a guard the picked driver cannot honor is announced rather than silently ignored. -**Start.** An agent starts from the dashboard composer, a routine's "Run now", a queue entry's play button, onboarding, or the CLI. The daemon resolves the project, allocates the workspace, guards against a busy project, and spawns the agent as a detached process. One aimed at a saved remote device is relayed to that device's daemon. +**Start.** The user starts an agent from the dashboard composer, a routine's "Run now", a queue entry's play button, onboarding, or the CLI. The daemon resolves the project, allocates the workspace, guards against a busy project, and spawns the agent as a detached process. An agent aimed at a saved remote device is relayed to that device's daemon. -**Workspace.** Every agent gets its own git worktree on its own branch, so concurrent agents never fight and the user's checkout — uncommitted work included — is never touched. Dependency directories are shared from the parent checkout instead of reinstalled. A non-git project falls back to the main checkout, one agent at a time; a git project whose worktree creation failed does **not** fall back — the start fails, because a failed agent is recoverable and a checkout with stray edits is not. +**Workspace.** The user's checkout — uncommitted work included — is never touched: every agent gets its own git worktree on its own branch, so concurrent agents never fight. Dependency directories are shared from the parent checkout instead of reinstalled. A non-git project falls back to the main checkout, one agent at a time; a git project whose worktree creation failed does **not** fall back — the start fails, because a failed agent is recoverable and a checkout with stray edits is not. -**Driving the work.** The driver runs each turn as a black box to completion. Everything The Framework learns from a turn, it learns by parsing the turn's final message: the session name the agent invented (the branch is renamed to match), agent-authored views for the dashboard, the ready-for-merge signal, and blocking gates. A build and a verbatim prompt are the same path — one opening prompt, honoring gates; what differs is which prompt opens the agent and whether its own backlog is worked afterwards. A hands-off location (a cloud session) drops every phase after the work is dispatched, because there is nothing to read back. +**Driving the work.** The work the user watches is driven turn by turn: the driver runs each turn as a black box to completion, and everything The Framework learns from a turn, it learns by parsing the turn's final message — the session name the agent invented (the branch is renamed to match), agent-authored views for the dashboard, the ready-for-merge signal, and blocking gates. Which CLI does that driving is a swappable axis (the *driver*), and where it executes is another (the *location*); the CLI keeps its own subscription auth, and The Framework never runs its own model calls for the coding work. A build and a verbatim prompt are the same path — one opening prompt, honoring gates; what differs is which prompt opens the agent and whether its own backlog is worked afterwards. A hands-off location (a cloud session) drops every phase after the work is dispatched, because there is nothing to read back. -**Gates and chat.** When the agent stops to ask, it parks and the question becomes a card: choices in the dashboard, a message on Discord, a notification. The answer travels back over the agent's control file and it continues from there — unless the answer is one the agent marked as ending it, which is how declining a plan stops the work rather than building on a rejected one. The human can also speak unprompted; each message continues the same conversation, and an idle attended agent stays open waiting for the next one. Unattended agents take the recommended answer instead of parking on a question nobody is there to answer. +**Gates and chat.** When the agent stops to ask, the user finds the question where it happened: the agent parks, and the question becomes a card — choices in the dashboard, a message on Discord, a notification. The answer travels back over the agent's control file (a file in its workspace the daemon appends to and the agent tails), and the agent continues from there. The one exception is an answer the agent marked as ending it — which is how declining a plan stops the work rather than building on a rejected one. The user can also speak unprompted; each message continues the same conversation, and an idle attended agent stays open waiting for the next one. Unattended agents take the recommended answer instead of parking on a question nobody is there to answer. -**The agent's own backlog.** Once the main work settles, the agent drains its queue file one entry per turn — read, complete exactly one entry, check it off, repeat — with a per-entry gate that an unattended agent answers itself. +**The agent's own backlog.** Once its main task is done, the agent drains its queue file one entry per turn — read, complete exactly one entry, check it off, repeat. The user is asked before each entry — a per-entry gate that an unattended agent answers itself. -**Settle and handoff.** Settling is strictly ordered: a final quality turn (which queues the quality presets as backlog entries and folds new learnings into the project docs) → the git handoff → close and archive the agent's history. The handoff runs only on the success path and first decides whether the agent is *empty* — no commits, or only bookkeeping files changed. Empty agents are never published; otherwise pending work is committed, the branch pushed, and a PR opened. Publishing is one ladder — keep it local, push, open a PR, merge — and each rung includes the ones below it. Unset means open a pull request: that is the zero-config promise, an agent left alone publishes itself. +**Settle and handoff.** The user receives finished work as a pull request: the handoff runs only on the success path — pending work is committed, the branch pushed, and a PR opened. The handoff first decides whether the agent is *empty* — no commits, or only bookkeeping files changed — and empty agents are never published. Settling is strictly ordered: a final quality turn (which queues the quality presets as backlog entries and folds new learnings into the project docs) → the git handoff → close and archive the agent's history. Publishing is one ladder — keep it local, push, open a PR, merge — and each rung includes the ones below it. Unset means open a pull request: that is the zero-config promise, an agent left alone publishes itself. -**Teardown and retention.** One rule decides every removal: the work is committed to the agent's branch, the branch is pushed, and the checkout goes only once the remote has it — so nothing local is ever the last copy, and every deletion is recoverable. A push that cannot land keeps the checkout, and the background sweep retries it later; a repo with nowhere to push keeps everything, which is the honest answer rather than a special case. The branch and the archived history always survive the worktree. An agent that died on a transient error is retried in the same worktree before being declared failed, and a finished one can be reopened later — its history restored so it continues as the same conversation. Acting on an agent the instant it finishes is safe: everything that touches its checkout takes its turn rather than racing, so a click that lands mid-teardown waits a beat instead of failing. +**Teardown and retention.** Whatever the user removes, the work survives: one rule decides every removal — the work is committed to the agent's branch, the branch is pushed, and the checkout goes only once the remote has it — so nothing local is ever the last copy, and every deletion is recoverable. A push that cannot land keeps the checkout, and the background sweep retries it later; a repo with nowhere to push keeps everything, which is the honest answer rather than a special case. The branch and the archived history always survive the worktree. An agent that died on a transient error is retried in the same worktree before being declared failed, and a finished one can be reopened later — its history restored so it continues as the same conversation. Acting on an agent the instant it finishes is safe: everything that touches its checkout takes its turn rather than racing, so a click that lands mid-teardown waits a beat instead of failing. -**Autonomy.** The repo-root queue file (`TODO_AGENTS.md`) is the durable, priority-ordered list of confirmed work — written directly by agents, unlike a *ticket*, which is a proposal for a human to accept. Because agents run in worktrees, the daemon promotes that one file back into the project checkout, committing only that file, and skipping with a stated reason whenever anything looks unexpected. An entry stays claimed while its agent is live or its PR is open, so parallel drains never double-assign it — the queue file itself is the record of what is left. On a timer, per project, the daemon asks one policy question — "is now a good time to spend quota on our own roadmap?" — checking the cheapest facts first. A non-empty queue is drained one entry per agent; an empty queue is refilled by rotating through quick triage → consensual triage → ticket planning. Ticket planning fans out several agents, each pinned to exactly one ticket and claimed via a lock file beside the ticket on the default branch, so agents on other machines see the claim too. A calendar-paced maintenance sweep sits outside the rotation and takes precedence when due. Every refusal is phrased as a reason, so a setting never reads as a bug. +**Autonomy.** The roadmap the daemon works while the user is away lives in one file: the repo-root queue file (`TODO_AGENTS.md`), the durable, priority-ordered list of confirmed work. Agents write it directly; a *ticket*, by contrast, is a proposal for a human to accept. Because agents run in worktrees, the daemon promotes that one file back into the project checkout, committing only that file, and skipping with a stated reason whenever anything looks unexpected. An entry stays claimed while its agent is live or its PR is open, so parallel drains never double-assign it — the queue file itself is the record of what is left. On a timer, per project, the daemon asks one policy question — "is now a good time to spend quota on our own roadmap?" — checking the cheapest facts first. A non-empty queue is drained one entry per agent; an empty queue is refilled by rotating through quick triage → consensual triage → ticket planning. Ticket planning fans out several agents, each pinned to exactly one ticket and claimed via a lock file beside the ticket on the default branch, so agents on other machines see the claim too. A calendar-paced maintenance sweep sits outside the rotation and takes precedence when due. Every refusal is phrased as a reason, so a setting never reads as a bug. -**Merging and CI watch.** Configuration only *arms* auto-merge; what *authorizes* it is the agent's own ready-for-merge signal plus an empty backlog of its own, and an armed-but-unauthorized merge is recorded as withheld, with the reason. The daemon polls the PRs the framework is waiting to land. Green checks on an armed PR: merge it — merge-on-green works even where GitHub's native auto-merge is off. Red checks: one unattended fix agent per failing head commit, told to land the fix on the PR's own branch; after two failed attempts the failure is evidently not one an agent can fix and a human keeps it. Housekeeping retires what has landed: worktrees whose branch merged are removed, and a pinned routine branch left behind by a closed PR is released so the routine can fire again. +**Merging and CI watch.** When the user arms auto-merge, configuration only *arms* it; what *authorizes* the merge is the agent's own ready-for-merge signal plus an empty backlog of its own. An armed-but-unauthorized merge is recorded as withheld, with the reason. The daemon polls the PRs the framework is waiting to land. Green checks on an armed PR: merge it — merge-on-green works even where GitHub's native auto-merge is off. Red checks: one unattended fix agent per failing head commit, told to land the fix on the PR's own branch; after two failed attempts the failure is evidently not one an agent can fix and a human keeps it. Housekeeping retires what has landed: worktrees whose branch merged are removed, and a pinned routine branch left behind by a closed PR is released so the routine can fire again. -**Spending limits.** The whole quota policy is one line: unattended work may spend up to the pro-rated share of the account's week that has elapsed, rising continuously with the clock. Nothing to configure — the week is read from the account itself. Two properties fall out: nothing is left on the floor (the boundary reaches the full allowance exactly as the week resets), and background work cannot starve the user (unattended work stands down past the boundary). A slider moves that stand-down line — but for work the user asked for, the slider only ever *loosens* the gate, and it is re-read live, so raising it unparks a waiting agent without a restart. The two gates fail in opposite directions on purpose: no readable quota means unattended work does not start, while user-requested work carries on. The gate is on *starting*, and only on starting: an agent already going is never interrupted to economise, because by then the tokens are spent, the work is half-done, and what is saved is the cheap part while what is lost is the expensive part. +**Spending limits.** The user never sets a budget, because the whole quota policy is one line: unattended work may spend up to the pro-rated share of the account's week that has elapsed, rising continuously with the clock. There is nothing to configure — the week is read from the account itself. Two properties fall out: nothing is left on the floor (the boundary reaches the full allowance exactly as the week resets), and background work cannot starve the user (unattended work stands down past the boundary). A slider moves that stand-down line — but for work the user asked for, the slider only ever *loosens* the gate, and it is re-read live, so raising it unparks a waiting agent without a restart. The two gates fail in opposite directions on purpose: no readable quota means unattended work does not start, while user-requested work carries on. The gate is on *starting*, and only on starting: an agent already going is never interrupted to economise — by then the tokens are spent, the work is half-done, and what is saved is the cheap part while what is lost is the expensive part. -**Surfaces.** The daemon serves the dashboard and answers all its reads from the files agents write. Non-local binds demand a shared token, because a daemon that spawns processes on a reachable port is remote code execution. For a saved remote device, the local daemon — never the browser — talks to the device's daemon and streams its events back over the local origin; the device's token is saved only in the user's own browser and handed to the local daemon per call. An agent can also run elsewhere: on a Claude cloud session (fire-and-forget: it opens its own PR), or on GitHub Actions (dispatch, poll, read back the uploaded transcript; continuity between turns is the branch the previous turn pushed) — with a browser extension inside the user's own claude.ai tab bridging cloud sessions back, so a question a cloud agent parks on becomes a dashboard card. An agent can launch a real Chrome that both it and a watching human attach to at once; when it hits a login wall, captcha, or 2FA it parks on a gate and hands the browser over — it never types a password. On Discord, notification watchers post agent activity and what needs a human; Discord is a way out, not a way in. +**Surfaces.** One daemon per machine serves the dashboard, and everything the user sees in it is read from the files agents write. Non-local binds demand a shared token, because a daemon that spawns processes on a reachable port is remote code execution. When the user targets a saved remote device, the local daemon — never the browser — talks to the device's daemon and streams its events back over the local origin. The device's token is saved only in the user's own browser and handed to the local daemon per call. An agent can also run elsewhere: on a Claude cloud session (fire-and-forget: it opens its own PR), or on GitHub Actions (dispatch, poll, read back the uploaded transcript; continuity between turns is the branch the previous turn pushed). A browser extension inside the user's own claude.ai tab bridges cloud sessions back, so a question a cloud agent parks on becomes a dashboard card. An agent can launch a real Chrome that both it and a watching user attach to at once; when it hits a login wall, captcha, or 2FA it parks on a gate and hands the browser over — it never types a password. On Discord, notification watchers post agent activity and what needs a human; Discord is a way out, not a way in. -**What lands in git.** One record of what happened: each agent's own event log, archived under a per-user directory keyed by the git identity, so cleaning the repo cannot erase the past and two people on one repo do not conflict. The daemon commits those archives after an idle window, only those paths, skipping while someone holds the index. Tickets — `tickets/_.md`, the human-facing roadmap, with optional plan and claim siblings, parsed tolerantly. And the queue file plus a human-readable log of what The Framework did to the project. +**What lands in git.** One record of what happened, kept in git where the user can always find it: each agent's own event log, archived under a per-user directory keyed by the git identity — so cleaning the repo cannot erase the past, and two people on one repo do not conflict. The daemon commits those archives after an idle window, only those paths, skipping while someone holds the index. Tickets — `tickets/_.md`, the human-facing roadmap, with optional plan and claim siblings, parsed tolerantly. And the queue file, plus a human-readable log of what The Framework did to the project. -**Prompts and presets.** One assembly path composes the system prompt for every agent — the built-in protocol, the extra protocol each capability brings, the user's own system file, and the picked context — and the exact composed text is recorded, so the dashboard can show precisely what the agent ran under. Two switches dial the wrapping down: *vanilla* drops the enhanced prompt while keeping the framework integration, and *transparent* is the master off-switch — no framework channel at all, the CLI raw. Presets (triage, research, security audit, drain-the-queue, …) are one catalog with prompt text authored as prose; custom presets save to either the user tier (follows the person, private) or the project tier (travels with the repo, shared). A per-repo config file records which preset and switches a project works under, resolved layer over layer. +**Prompts and presets.** The user can read precisely what an agent ran under, because one assembly path composes the system prompt for every agent — the built-in protocol, the extra protocol each capability brings, the user's own system file, and the picked context — and the exact composed text is recorded. Two switches dial the wrapping down: *vanilla* drops the enhanced prompt while keeping the framework integration, and *transparent* is the master off-switch — no framework channel at all, the CLI raw. Presets (triage, research, security audit, drain-the-queue, …) are one catalog with prompt text authored as prose; custom presets save to either the user tier (follows the person, private) or the project tier (travels with the repo, shared). A per-repo config file records which preset and switches a project works under, resolved layer over layer. ## Rationales @@ -76,14 +87,19 @@ flowchart TD ## Glossary +- **agent** — one task worked by one spawned run of the driver, streaming events as it goes; the unit that is watched, steered, published, retried, and archived. - **driver** — the coding-agent CLI that does the work (Claude Code today, Codex too); swappable per agent. - **location** — where an agent executes: the local machine, a saved remote device, a GitHub Actions runner, or a Claude cloud session. - **attended / unattended** — whether a human is expected at the keyboard: an attended agent parks on its questions; an unattended one takes the recommended answer and carries on. +- **composer** — the dashboard's prompt box: type a prompt or pick a preset to start an agent, or send the next message to a live one. - **gate** — a question an agent parks on, shaped as options; an option can be marked to stop the agent rather than resume it. - **ticket** — a proposal for a human to accept, kept as a file under `tickets/`. - **queue entry** — one item of confirmed work in the repo-root queue file `TODO_AGENTS.md`. -- **empty agent** — an agent whose run left no commits (or only bookkeeping changes); it publishes nothing. +- **routine** — a recurring unattended job the daemon fires on its own — the queue drain, the ticket-refill rotation, the maintenance sweep; each can be switched off individually, and "Run now" fires one by hand. - **preset** — a cataloged prompt that starts an unattended agent (triage, research, security audit, …). +- **empty agent** — an agent whose run left no commits (or only bookkeeping changes); it publishes nothing. +- **handoff** — how far a finished agent publishes its work: the ladder keep-it-local → push → open a PR → merge, where each rung includes the ones below it; unset means a PR. +- **quota week** — the subscription's own weekly usage window, read from the account's quota readout; unattended spending is measured against the share of it that has elapsed. ## Before modifying/creating SPEC.md files From c6c14cc7b587103c51ab9b8d8578776a9945aa39 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 09:09:07 +0000 Subject: [PATCH 09/16] Style pass: re-voice the GitHub workflow specs to the user-story axis The Actions runner spec opens from the user running an agent on a GitHub-hosted runner, states the push guarantees the workflow carries, and names the framework as the one actor; the transcript-artifact claim is sharpened to the branch/artifact split the yml itself makes. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- .github/workflows/SPEC.md | 10 +++++++--- .github/workflows/framework-agent.SPEC.md | 14 ++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/SPEC.md b/.github/workflows/SPEC.md index a3a40f1da..62aac1616 100644 --- a/.github/workflows/SPEC.md +++ b/.github/workflows/SPEC.md @@ -1,9 +1,13 @@ -The repo's GitHub Actions workflows: continuous integration, the dispatchable agent runner, and website deployment. +The repo's three GitHub Actions workflows — one is part of the product (the agent runner), two are repo plumbing (continuous integration and website deployment). + +## User Stories + +- The user runs an agent on a fresh GitHub Actions runner instead of their own machine. ## Flows -- `framework-agent.yml` is the only workflow that is part of the product runtime — the daemon dispatches it to run agent turns on cloud runners; the rest is repo plumbing. -- `ci.yml` builds, typechecks, and tests every push and PR; `website-deploy.yml` publishes the-framework.ai. +- When the user runs an agent on GitHub Actions instead of their own machine, the framework dispatches `framework-agent.yml` — one dispatch per agent turn, each on a fresh GitHub-hosted runner. +- `ci.yml` builds, typechecks, and tests every push and PR; `website-deploy.yml` publishes the-framework.ai whenever a change to the website lands on `main`. ## Rationales diff --git a/.github/workflows/framework-agent.SPEC.md b/.github/workflows/framework-agent.SPEC.md index df6c1974b..0f7fe0d66 100644 --- a/.github/workflows/framework-agent.SPEC.md +++ b/.github/workflows/framework-agent.SPEC.md @@ -1,4 +1,4 @@ -The workflow the product dispatches to run one coding-agent turn on a disposable GitHub-hosted runner — how an agent runs "elsewhere" on GitHub Actions. +The workflow the framework dispatches to run one coding-agent turn on a disposable GitHub-hosted runner — the runner-side half of running an agent on GitHub Actions instead of the user's machine. ## User Stories @@ -6,15 +6,17 @@ The workflow the product dispatches to run one coding-agent turn on a disposable ## Flows -- One dispatch is one agent turn. The daemon tags the run with a correlation id of its own making and finds the run by that tag. -- The run spends the user's own driver subscription, not an API key, and the agent runs unrestricted. -- The turn's work — including anything left uncommitted — is pushed to the branch the driver chose, so the next turn continues exactly where this one stopped. -- The transcript comes back as an uploaded artifact, the only channel out of a run; a failed turn still uploads it. +- One dispatch is one agent turn. The framework tags the run with a correlation id of its own making and finds the run by that tag. +- The run spends the user's own Claude subscription — an OAuth token the user minted and the repository holds — never an API key. +- The agent runs unrestricted: it edits files and runs commands without waiting on permission prompts that nobody is there to answer. +- The turn's work — including anything the agent left uncommitted — is pushed to the branch the framework chose, so nothing is lost with the runner and the next turn continues exactly where this one stopped. A failed turn's work is pushed too. A turn that changed nothing pushes nothing, so a no-op run creates no branch. +- The run's report comes back as one uploaded artifact: the transcript, which branch was pushed, and the agent's session id so a later turn can resume the same conversation. Besides the pushed branch, that artifact is the only thing that leaves the runner; a failed turn still uploads it. ## Rationales -- The daemon invents its own correlation id because GitHub's dispatch call never reveals which run it started. +- The framework invents its own correlation id because GitHub's dispatch call never reveals which run it started. - The agent runs unrestricted because the runner is disposable: nothing on it outlives the turn. +- The workflow pushes the branch itself because the action that runs the agent creates none for a dispatched run — and publishing is the framework's job, as it is locally: the agent only commits. - A failed turn still uploads its transcript because a failure is exactly when the transcript is most wanted. ## Before modifying/creating SPEC.md files From 0e511183796672c44985f8fe7bf81af6a4a1b9e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 09:11:31 +0000 Subject: [PATCH 10/16] Style pass: re-voice the chrome extension and website specs to the user-story axis Extension flows open from the user's pick and name the dashboard-side effect; decoy rules stated observable-first; the options page gains its own two stories (a surface no parent story covers); cryptic parentheticals unpacked; the site's CTA sentence split. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/chrome-extension/SPEC.md | 6 +++--- packages/chrome-extension/background.SPEC.md | 8 ++++---- packages/chrome-extension/content.SPEC.md | 7 ++++--- packages/chrome-extension/options.SPEC.md | 11 ++++++++--- packages/the-framework.ai/SPEC.md | 2 +- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/chrome-extension/SPEC.md b/packages/chrome-extension/SPEC.md index 9c98ee8a4..594ceb98f 100644 --- a/packages/chrome-extension/SPEC.md +++ b/packages/chrome-extension/SPEC.md @@ -8,10 +8,10 @@ A Chrome extension bridging Claude Code cloud sessions on claude.ai to the local ## Flows -- A cloud agent hands off and ends, so when its session later asks something, nothing streams back and the question strands on claude.ai; the extension reads the session page the user is already signed into and carries the question — plus a mirror of the transcript — to the daemon. +- A cloud agent hands off and ends, so when its session later asks something, nothing streams back — the question strands on claude.ai. The extension reads the session page the user is already signed into and carries the question — plus a mirror of the transcript — to the daemon, so the dashboard shows both. - Two halves with a strict trust line: the page script reads claude.ai and types into it but never holds a secret; the background worker holds the bridge token and is the only part that talks to the daemon. -- Answers go the long way round: dashboard pick → daemon queue → worker → typed into the session's composer and submitted; the extension can only ever type a label the session itself offered, and the pick was confirmed in the dashboard first. -- It keeps one pinned background tab per session the daemon watches, so the bridge works with nobody looking at claude.ai — closing tabs when watching stops, and never reopening one the user closed. +- The user's answer goes the long way round: dashboard pick → daemon queue → worker → typed into the session's composer and submitted. The extension can only ever type a label the session itself offered, and the pick was confirmed in the dashboard first. +- The bridge works with nobody looking at claude.ai: the extension keeps one pinned background tab per session the daemon watches, closes them when watching stops, and never reopens one the user closed. - Reading is driven by page changes with a slow heartbeat backstop, and every stage reports its status — on the page's panel and in the settings page's connection test — so a silent misconfiguration is visible. - The extension and the daemon insist on matching versions: every call states the extension's version, and a daemon expecting another blocks it with an error naming both and the update path. diff --git a/packages/chrome-extension/background.SPEC.md b/packages/chrome-extension/background.SPEC.md index 800b80fe7..503019417 100644 --- a/packages/chrome-extension/background.SPEC.md +++ b/packages/chrome-extension/background.SPEC.md @@ -2,10 +2,10 @@ The extension's daemon half: the only part holding the bridge token and talking ## Flows -- Questions forward with a dedupe: the page re-reports on every change, and an unchanged question for the same session costs nothing. -- Answers travel back: queued picks are fetched on a fast beat, handed to the page in that session's tab to type, and the outcome reported — typing before reporting, so a pick is never marked sent that a dying tab never typed; failed deliveries and reports are retried, not dropped. -- One pinned, inactive tab opens per session the daemon says to watch; stale ones close, and a tab the user closed is never reopened. -- Every sweep records why it did or didn't act, so "tabs are not opening" is answerable from the options page. +- Questions forward with a dedupe: the page half re-reports on every page change, and an unchanged question for the same session is not re-sent to the daemon. +- Answers travel back: each answer the user picks in the dashboard is queued at the daemon, fetched on a fast beat, and handed to the page half in that session's tab to type, and the outcome is reported back. Typing comes before the report, so an answer is never marked sent that a dying tab never typed. Failed deliveries and failed reports are retried, not dropped. +- One pinned, inactive tab opens per session the daemon says to watch; a tab whose session is no longer watched closes, and a tab the user closed is never reopened. +- Every sweep — each pass that opens and closes these tabs — records why it did or didn't act, so "tabs are not opening" is answerable from the options page. - Every daemon call states this extension's version; a daemon expecting another refuses outright with both versions named, so a stale install blocks loudly instead of half-working. ## Rationales diff --git a/packages/chrome-extension/content.SPEC.md b/packages/chrome-extension/content.SPEC.md index 7f86ff337..e009eddb5 100644 --- a/packages/chrome-extension/content.SPEC.md +++ b/packages/chrome-extension/content.SPEC.md @@ -2,9 +2,10 @@ The extension's page half: injected into claude.ai session pages, it finds the q ## Flows -- The question is the JSON options block our agents emit, brace-matched out of surrounding prose wherever it hides — code elements, shadow roots, split across highlighter spans — and the page's rendered copy of our own protocol is a decoy three ways (#1568): nothing inside the transcript's opening message counts (that is the prompt rendering, examples included), a placeholder-shaped title is the spec talking even when punctuation joins the placeholders, and the protocol's two literal examples are matched verbatim; the last real question among what survives wins. -- The transcript mirrors as per-message blocks when the page marks them, else as the visible conversation text from the newest end (application chrome and our own panel stripped); only what changed since the last look is sent. -- Delivering an answer is the one action taken: the pick is typed into the composer and submitted, after patiently waiting for a slow page to render the composer; only the top frame types, so nothing submits twice — and only labels the session itself offered can ever be typed. +- The question is the JSON options block an agent emits when it parks on a decision, brace-matched out of surrounding prose wherever the page hides it — code elements, shadow roots, split across highlighter spans. +- The page also renders the run's own prompt, which quotes that block's protocol — examples included — so the dashboard is never handed a decoy (#1568): nothing inside the transcript's opening message counts (that is the prompt rendering), a placeholder-shaped title is the protocol description talking (even when punctuation joins the placeholders), and the protocol's two literal examples are matched verbatim. The last real question among what survives wins. +- The transcript mirrors to the dashboard as per-message blocks when the page marks them, else as the visible conversation text read from the newest end — claude.ai's surrounding UI and the extension's own panel stripped out. Only what changed since the last look is sent. +- Delivering an answer is the one action taken: the answer the user picked in the dashboard is typed into the composer and submitted, after waiting out a slow page still rendering the composer. Only the top frame types, so nothing submits twice — and only a label the session itself offered can ever be typed. - It re-reads on page mutation with a slow heartbeat backstop, and its collapsible panel reports every stage's status — found, sent, delivered, and why not. ## Rationales diff --git a/packages/chrome-extension/options.SPEC.md b/packages/chrome-extension/options.SPEC.md index d509344b2..b329da04e 100644 --- a/packages/chrome-extension/options.SPEC.md +++ b/packages/chrome-extension/options.SPEC.md @@ -1,10 +1,15 @@ -The settings page: the daemon address, the bridge token, and the tab-opening switch live in extension storage — never in the page — and saving immediately proves the connection instead of just storing it. +The settings page: the daemon address, the bridge token, and the tab-opening switch live in extension storage — out of reach of any web page — and saving immediately proves the connection instead of just storing it. + +## User Stories + +- The user saves the daemon address and the bridge token, and immediately sees the connection proven or the exact failure named. +- The user switches automatic tab-opening on or off, and opens the tabs on demand instead of waiting on the timer. ## Flows -- The test names the exact failure: Chrome not actually granting site access (declared is not granted, and without it the daemon sees nothing), daemon unreachable, token rejected, a version the daemon refuses (its answer, naming both versions and the way out, is shown verbatim), bridge switched off, or a dashboard too old to have a bridge — whose look-alike success page is not accepted as connected. +- The test names the exact failure: Chrome not actually granting site access (a site the extension declares is not automatically a site Chrome granted, and without the grant no request ever leaves the browser), daemon unreachable, token rejected, a version the daemon refuses (its answer, naming both versions and the way out, is shown verbatim), the bridge switched off in The Framework, or a dashboard too old to have a bridge — that last one answers with its own app page, a look-alike success not accepted as connected. - Success also says how many cloud sessions the daemon is watching, so "connected but nothing happens" answers itself. -- A button runs the tab sweep on demand and reports what it did, sparing the wait on the timer. +- A button runs the tab sweep — the pass that opens a pinned tab per watched cloud session — on demand and reports what it did, sparing the wait on the timer. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework.ai/SPEC.md b/packages/the-framework.ai/SPEC.md index dd7459ea4..6976b5ba2 100644 --- a/packages/the-framework.ai/SPEC.md +++ b/packages/the-framework.ai/SPEC.md @@ -10,7 +10,7 @@ The product's marketing website, the-framework.ai — it pitches The Framework a ## Flows - One long landing page carries the whole pitch: stop babysitting AI, make the important decisions, let AI do the rest. -- The main call to action everywhere is a copy-pasteable terminal command that runs The Framework; visitors pick their package manager once and every command site-wide shows in that flavor, remembered across visits. +- The main call to action everywhere is a copy-pasteable terminal command that runs The Framework. Visitors pick their package manager once, every command site-wide switches to it, and the choice is remembered across visits. - Three side pages support it: a press page with brand material, a go-to-dashboard page explaining that the dashboard runs on the visitor's own machine, and a hidden banner page that gets screenshotted into the social-preview image. - The whole site is pre-rendered into plain static pages — no server. From 10450108b9efdb37a6e89ef605d63353f0ce1d55 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 09:12:46 +0000 Subject: [PATCH 11/16] Style pass: re-voice the src directory, agent-helper, and browser specs The src directory spec's flows open from the user's side with its jargon glossed; agent-messages, agent-view, browser, and browser-stream gain the stories they embody (chat mid-run, take over Chrome at a login wall, armed handoff at a glance); plumbing keeps system framing with effects named. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/src/SPEC.md | 14 +++++++------- packages/the-framework/src/agent-archive.SPEC.md | 4 ++-- packages/the-framework/src/agent-driver.SPEC.md | 4 ++-- packages/the-framework/src/agent-location.SPEC.md | 2 +- packages/the-framework/src/agent-messages.SPEC.md | 11 ++++++++--- packages/the-framework/src/agent-view.SPEC.md | 12 +++++++++--- packages/the-framework/src/await-gate.SPEC.md | 4 ++-- packages/the-framework/src/browser-stream.SPEC.md | 10 ++++++++-- packages/the-framework/src/browser.SPEC.md | 5 +++++ 9 files changed, 44 insertions(+), 22 deletions(-) diff --git a/packages/the-framework/src/SPEC.md b/packages/the-framework/src/SPEC.md index 543407235..c29bf9f53 100644 --- a/packages/the-framework/src/SPEC.md +++ b/packages/the-framework/src/SPEC.md @@ -19,12 +19,12 @@ flowchart LR Handoff --> Watch["CI watch: merge green,
fix red, retire merged"] ``` -- One daemon per machine spawns and tracks agents, serves the localhost dashboard, and runs the background services (autonomy sweeps, CI watch, notifications, chat) on one shared clock rather than a timer apiece. Files are the seam: agents narrate onto an on-disk event stream, steering comes back over an append-only control file, and the dashboard is a projection of what they wrote — never a live wire into them. -- An agent is one task worked in its own git worktree on its own branch. The CLI behind it (Claude Code today) stays a swappable black box behind the driver seam; everything the framework learns from a turn — ask-gates, views, the session name, ready-for-merge — is parsed as tagged blocks out of the turn's final message, and a single gate block carries every kind of question. -- A build and a verbatim prompt are one path: an opening prompt that honors gates, differing only in which prompt opens it and whether the agent's own backlog is worked afterwards. Nothing reviews the work — the agent is a black box and its turn is the whole of it. -- When the agent stops to ask, the question becomes a card on every surface and the picked answer re-prompts the same conversation — unless the agent marked that answer as one that ends it, which is how a declined plan stops the work rather than building on a rejection. An unattended agent takes the recommended option, and a hands-off one is told up front the gates are unavailable so it never parks on a question nobody can answer. -- One composition path assembles every agent's system channel (project context and knowledge docs, built-in prompt, the user's own instructions, the emit protocols), so the dashboard can show exactly what the agent ran under; two switches dial the wrapping down — vanilla drops the built-in prompt, transparent empties the channel entirely. -- An agent that ends with real work publishes itself — commit, push, open a PR; empty ones publish nothing, and merging is authorized by the agent's own ready signal plus an empty backlog of its own, never by configuration alone. How far it publishes is one ordinal, not a set of switches, so an impossible combination cannot be represented. +- The dashboard the user watches is served on localhost by one daemon per machine, which also spawns and tracks the agents and runs the background services (autonomy sweeps, CI watch, notifications, chat) on one shared clock rather than a timer apiece. Files are the seam: agents narrate onto an on-disk event stream, steering comes back over an append-only control file, and the dashboard is a projection of what they wrote — never a live wire into them. +- An agent is one task worked in its own git worktree on its own branch — which is why the user's own checkout is never touched. The CLI behind it (Claude Code today) stays a swappable black box behind the driver seam. Everything the framework learns from a turn — the question the agent stopped to ask, the views it pushed to the dashboard, the session name it chose, its ready-for-merge signal — is parsed as tagged blocks out of the turn's final message, and a single gate block carries every kind of question. +- Building from the user's intent and running their prompt verbatim are one path: an opening prompt that honors gates. The two differ only in which prompt opens the conversation and whether the agent's own backlog is worked afterwards. Nothing reviews the work — the agent is a black box and its turn is the whole of it. +- When the agent stops to ask, the user sees the question as a card on every surface, and the answer they pick re-prompts the same conversation — unless the agent marked that answer as one that ends it, which is how a declined plan stops the work rather than building on a rejection. An unattended agent takes the recommended option, and an agent handed off somewhere this machine cannot follow (a cloud session) is told up front the gates are unavailable, so it never parks on a question nobody can answer. +- The user can read the exact system prompt any agent ran under, because one composition path assembles every agent's system channel (project context and knowledge docs, built-in prompt, the user's own instructions, the emit protocols). Two switches dial the wrapping down — vanilla drops the built-in prompt, transparent empties the channel entirely. +- The pull request the user gets back is the agent publishing itself: an agent that ends with real work commits, pushes, and opens a PR; an empty one publishes nothing. Merging is authorized by the agent's own ready signal plus an empty backlog of its own, never by configuration alone. How far an agent publishes is one ordinal — each rung including the ones below it — not a set of switches, so an impossible combination cannot be represented. - When nobody is around, the daemon plays product manager bounded by the account's own quota week: drain the confirmed queue, refill it by triaging and planning tickets (claims committed as lock files beside the tickets, so other machines and cloud agents see them), keep CI green on the PRs it opened, and merge on green. - Unattended spending stands down past the pro-rated share of the account's week that has elapsed; work the user asked for carries on. The gate is on starting and only on starting — an agent already going is never interrupted to economise. - What must outlive a process lands in git, not memory: each agent's own event log, archived per user so a repo clean cannot erase it and teammates never conflict, plus tickets and their claims, the queue, and the project log. @@ -32,7 +32,7 @@ flowchart LR ## Rationales -- Worktrees exist so concurrent agents never fight and the user's checkout — uncommitted work included — is never touched; one retention rule decides every removal, and it asks whether the work is on the remote rather than how the agent ended, so cleanup commits and pushes before removing anything and every deletion is recoverable. +- Worktrees exist so concurrent agents never fight and the user's checkout — uncommitted work included — is never touched. One retention rule decides every removal, and it asks whether the work is on the remote rather than how the agent ended: cleanup commits and pushes before removing anything, so every deletion is recoverable. - The CLI is treated as a black box on purpose: the framework never runs its own model calls for the coding work, and swapping which CLI does the work means swapping one driver. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/agent-archive.SPEC.md b/packages/the-framework/src/agent-archive.SPEC.md index d41f1f486..2164d03b1 100644 --- a/packages/the-framework/src/agent-archive.SPEC.md +++ b/packages/the-framework/src/agent-archive.SPEC.md @@ -1,8 +1,8 @@ -Names the identity a finished agent's history is filed under, so the archive on the data branch is per user and two people on one repo never conflict. +Names the identity a finished agent's history is filed under, so the archive on the data branch (`tf-data`, where the framework commits its own records) is per user and two people on one repo never conflict. ## Flows -- Each person's history files under a directory named after the git email they already commit with — nothing new to set up. A hostile or unusable value can never escape the archive path; it falls back to an "anonymous" directory rather than dropping history. +- Each person's history files under a directory named after the git email they already commit with — nothing new to set up. An email that cannot become a safe directory name — hostile, empty, or unreadable — files under an "anonymous" directory instead: history is never dropped, and no configured value can ever climb out of the archive path. ## Rationales diff --git a/packages/the-framework/src/agent-driver.SPEC.md b/packages/the-framework/src/agent-driver.SPEC.md index 755d4bcb8..5cdef6f93 100644 --- a/packages/the-framework/src/agent-driver.SPEC.md +++ b/packages/the-framework/src/agent-driver.SPEC.md @@ -1,8 +1,8 @@ -Turns an agent's chosen target into the driver that executes it: this device by default, a fresh GitHub Actions runner, or a Claude cloud session. +Turns the run target the user picked for an agent into the driver that executes it: this device by default, a fresh GitHub Actions runner, or a Claude Code cloud session. ## Flows -- The Actions target needs the repo's owner, name, and a token, and fails fast with a clear message without them; the cloud target needs nothing extra, because the agent's own signed-in account carries it. +- When the user targets a GitHub Actions runner, the repo's owner, name, and a token are required; without them the start fails fast with a message naming what to set. Targeting a cloud session needs nothing extra, because the agent's own signed-in account carries it. ## Rationales diff --git a/packages/the-framework/src/agent-location.SPEC.md b/packages/the-framework/src/agent-location.SPEC.md index 6580ca918..1725caa23 100644 --- a/packages/the-framework/src/agent-location.SPEC.md +++ b/packages/the-framework/src/agent-location.SPEC.md @@ -2,7 +2,7 @@ Where an agent's turns execute — this device, a fresh CI runner, or a cloud se ## Flows -- Whether an agent hands the task somewhere this machine cannot follow is a fact about the location, not about the agent. Only a cloud session does: it opens its own pull request and never reports back, so the first prompt is the whole agent and every later phase would misread the hand-off note as the agent's own reply. A CI runner streams its agent's replies and is followed like a local agent. +- Whether an agent hands the task somewhere this machine cannot follow is a fact about the location, not about the agent. Only a cloud session does: it opens its own pull request and never reports back, so the first prompt is the whole agent. Every later phase would misread the driver's hand-off note as the agent's own reply — and would show the user questions nobody here can answer, asked on behalf of an agent that is somewhere else. A CI runner streams its agent's replies and is followed like a local agent. ## Rationales diff --git a/packages/the-framework/src/agent-messages.SPEC.md b/packages/the-framework/src/agent-messages.SPEC.md index e43b111d0..7b40484bf 100644 --- a/packages/the-framework/src/agent-messages.SPEC.md +++ b/packages/the-framework/src/agent-messages.SPEC.md @@ -1,10 +1,15 @@ The live-chat channel into a running agent: the user's own messages, spoken unprompted, each continuing the same agent conversation with its full context. +## User Stories + +- The user chats with a running agent; each message continues the same conversation with its full context. +- The user comes back to a daemon-managed agent that already ended, sends another message, and the same conversation reopens. + ## Flows -- The reverse of the agent asking the user: here the user speaks first, and the agent drains the queue once its current work settles. -- A daemon-managed agent ends itself when the queue is idle — a later message reopens the conversation — while an agent whose own terminal is the only surface stays parked, since it has no daemon to resume through. -- Stopping or closing wakes every waiter empty so the agent ends cleanly, and a stale message never starts a turn on an aborted agent. +- The reverse of the agent asking the user: here the user speaks first, and the agent drains the queued messages once its current work settles. +- A daemon-managed agent ends itself when no more messages wait — a later message reopens the conversation — while an agent whose own terminal is the only surface stays parked for the next message, since it has no daemon to resume through. +- A stop or a close wakes every parked wait empty so the agent ends cleanly, and a message already queued never starts a turn on an aborted agent. - A headless agent gets no channel at all and ends when it stops asking. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/agent-view.SPEC.md b/packages/the-framework/src/agent-view.SPEC.md index e699a0aa3..345d6b87b 100644 --- a/packages/the-framework/src/agent-view.SPEC.md +++ b/packages/the-framework/src/agent-view.SPEC.md @@ -1,12 +1,18 @@ Distills an agent's event stream into the dashboard's summary cards: the agent's lifecycle progress, what it will do with its work when it ends, and the agent session behind it. +## User Stories + +- The user reads an agent's card at a glance: the name it chose, and a badge that flips when the agent signals ready for merge. +- The user sees what an agent will do with its work when it ends — push, open a pull request, merge — exactly as armed. +- The user opens a past agent's record and sees the identical summary a live viewer saw. + ## Flows -- Pure folds over the same events the log renders, so a live dashboard and a replay of a past agent always show the identical summary. +- The cards are pure folds over the same events the log renders, so a live dashboard and a replay of a past agent always show the identical summary. - Latest wins throughout: the agent may rename its session or re-arm its handoff at any point. - The publish state reads as armed (push and pull request) even for a stream that never says so, because that is what such an agent will actually do; merging is the opposite — opt-in, so silence reads as off. -- A stored snapshot can seed the publish state for a viewer who attached after the agent's opening events, but an event in the stream always wins over it. -- The workspace comes from the events rather than the disk: a finished agent's working copy is deleted, and the event is the surviving record of where the conversation lived — which resuming it needs. +- A stored snapshot can seed the publish state for a viewer who attached after the agent's opening events — so a session the launcher armed push-only never shows as one that will open a pull request; an event in the stream always wins over the snapshot. +- Resuming a finished agent needs to know where the conversation lived, and its working copy is deleted — so the workspace comes from the events, the surviving record, never from the disk. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/await-gate.SPEC.md b/packages/the-framework/src/await-gate.SPEC.md index 8cf6050da..69f1b2d48 100644 --- a/packages/the-framework/src/await-gate.SPEC.md +++ b/packages/the-framework/src/await-gate.SPEC.md @@ -2,8 +2,8 @@ The shared pause-and-ask machinery: an agent can park mid-conversation to ask th ## Flows -- One resolution path: a gate shows its options, waits for the pick, and re-prompts the agent with the answer, repeating until the agent stops asking — capped so it cannot ask forever. A gate that takes several picks answers with the labels it got; every other gate answers with the one label picked. -- Almost every answer goes back to the agent. The exception is an answer the agent marked as ending it — declining a plan being the one that matters, because the user's next move is fresh instructions and building on a plan they just rejected is the worst thing to do with the interval. On a gate taking several picks, one stopping pick among several still stops. +- One resolution path: a gate (one parked question) shows its options, waits for the pick, and re-prompts the agent with the answer, repeating until the agent stops asking — capped so it cannot ask forever. A gate that takes several picks answers with the labels it got; every other gate answers with the one label picked. +- Almost every answer goes back to the agent. The exception is an answer the agent marked as ending it — declining a plan being the one that matters: the user's next move is fresh instructions, and building on a plan they just rejected is the worst use of the time until those arrive. On a gate taking several picks, one stopping pick among several still stops. - With nobody to answer (headless, or stopped mid-question) a gate falls back to its recommended option so it never hangs. Which option that is belongs to the agent: handing over a browser recommends "could not handle it", because claiming a human cleared a login wall nobody saw sends the agent back to a blocked page. - Once the agent stops asking, live chat takes over: each user message resumes the same conversation; by default the agent finishes what queued and ends itself — only one whose own terminal is the single surface stays parked for the next message. diff --git a/packages/the-framework/src/browser-stream.SPEC.md b/packages/the-framework/src/browser-stream.SPEC.md index ee0cbcfb1..e8ee4d3b8 100644 --- a/packages/the-framework/src/browser-stream.SPEC.md +++ b/packages/the-framework/src/browser-stream.SPEC.md @@ -1,9 +1,15 @@ Streams the agent's headless browser to a human — the latest screen frame as a simple image stream, with clicks, typing, scrolling and navigation posted back — so when an agent parks asking someone to deal with a login wall or captcha, there is actually a page to act on. +## User Stories + +- The user watches, live in the dashboard, the very page the agent's browser is on. +- When the agent parks on a login wall, captcha, or 2FA step, the user clicks, types, scrolls, and navigates in the agent's own browser to clear it. +- The user types a password through the pane and no frame of it is ever stored. + ## Flows -- Follows the agent: always shows the tab it is working in, re-attaching when it opens or switches tabs, and announces each real page change so the transcript can show the pane at the point of use. -- A still page is exactly the parked case, so the newest frame is re-sent on a timer to keep the pane painted. +- The pane follows the agent: the user always sees the tab the agent is working in — re-attaching when the agent opens or switches tabs — and each real page change is announced so the transcript can show the pane at the point of use. +- A parked agent's page is still, and a still page emits no new frame — so the newest frame is re-sent on a timer and the user sees the page rather than a blank pane. - Loopback only, and no frame is ever persisted: frames can show a password being typed. - Malformed input never reaches the browser; a browser with no page, or one that stops answering, costs the pane, never the agent. diff --git a/packages/the-framework/src/browser.SPEC.md b/packages/the-framework/src/browser.SPEC.md index 00063b440..2bba917d0 100644 --- a/packages/the-framework/src/browser.SPEC.md +++ b/packages/the-framework/src/browser.SPEC.md @@ -1,5 +1,10 @@ The agent launches its own browser with a shared remote-control port, so the agent's browser tools and a human's preview can watch the very same page — a second viewer cannot attach to a browser that launched privately. +## User Stories + +- The user watches the very page the agent's browser tools are driving, and can be handed that browser at a gate — both attach to the same Chrome. +- The user's real browser session is never inherited or dirtied: the agent's Chrome is headless, on a throwaway profile. + ## Flows - Finds the machine's Chrome (explicit override first, then the well-known locations, then the PATH); none found means the agent's browser tools fall back to launching their own — a missing browser costs the preview, never the tools. From 531b25152b36ec093dc6d1acce1bb554cd6a9965 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 09:14:24 +0000 Subject: [PATCH 12/16] Style pass: re-voice the daemon dashboard reads (G-Z) to the user-story axis Every read model gains the stories of what the user sees (the online dot, the overview widgets, the bind choice and its token), flows open from that surface, guards and null-prototype behavior stated observable-first, and coined terms glossed inline. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/src/dashboard/gh.SPEC.md | 10 ++++++++-- .../the-framework/src/dashboard/git-status.SPEC.md | 6 ++++++ .../src/dashboard/interventions.SPEC.md | 8 +++++++- .../src/dashboard/keyed-watcher.SPEC.md | 9 +++++++-- .../the-framework/src/dashboard/open-in-app.SPEC.md | 9 ++++++++- .../src/dashboard/open-questions.SPEC.md | 8 +++++++- .../the-framework/src/dashboard/overview.SPEC.md | 10 ++++++++-- .../the-framework/src/dashboard/projects.SPEC.md | 10 ++++++++-- packages/the-framework/src/dashboard/queue.SPEC.md | 5 +++++ packages/the-framework/src/dashboard/quota.SPEC.md | 10 ++++++++-- .../src/dashboard/relay-endpoints.SPEC.md | 12 +++++++++--- .../the-framework/src/dashboard/remote-run.SPEC.md | 8 +++++++- .../the-framework/src/dashboard/rpc-serve.SPEC.md | 11 ++++++++--- packages/the-framework/src/dashboard/server.SPEC.md | 12 +++++++++--- packages/the-framework/src/dashboard/tickets.SPEC.md | 12 +++++++++--- 15 files changed, 114 insertions(+), 26 deletions(-) diff --git a/packages/the-framework/src/dashboard/gh.SPEC.md b/packages/the-framework/src/dashboard/gh.SPEC.md index 8fde825ca..ec9214277 100644 --- a/packages/the-framework/src/dashboard/gh.SPEC.md +++ b/packages/the-framework/src/dashboard/gh.SPEC.md @@ -1,13 +1,19 @@ Everything the dashboard asks or tells GitHub — pull request lookups, merging, CI status — in one place. +## User Stories + +- The user sees an agent's pull request and its CI state on the dashboard; when GitHub is unreachable, panels show nothing rather than a failed page. +- The user is never shown a predecessor's merged PR as their agent's own when a branch name is reused. +- The user asks for a merge and it goes through even where the repo refuses GitHub's auto-merge; the automatic merge path never lands unverified work. + ## Flows - Reads are quick, cached, and forgiving: an unreachable GitHub reads as nothing, never a failed page. - An agent's PR is picked from its branch's whole history — an open PR always counts, a closed one only if created after the agent began — so a reused branch name cannot wear a predecessor's merged PR. -- Merging prefers GitHub's auto-merge, so the PR lands when its checks pass; where the repo refuses, a human-requested merge goes through directly, while the automatic path merges only on green checks and otherwise hands the PR to the daemon's CI watch — unverified work never lands. +- Merging prefers GitHub's auto-merge, so the PR lands when its checks pass. Where the repo refuses auto-merge, a merge the user asked for goes through directly; the automatic path merges only on green checks and otherwise hands the PR to the daemon's CI watch, which merges it once its checks pass — unverified work never lands. - A draft in the way is marked ready and retried: asking for the merge says its review already happened. - CI status boils down to passing, failing (naming the failures), pending, or none; an unreadable status is never green. -- A cloud agent authenticates with a token from the environment or, failing that, the user's existing GitHub login. +- An agent sent to a GitHub Actions runner authenticates with a token from the environment or, failing that, the user's existing GitHub login. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/git-status.SPEC.md b/packages/the-framework/src/dashboard/git-status.SPEC.md index 7e1a11204..2762f343f 100644 --- a/packages/the-framework/src/dashboard/git-status.SPEC.md +++ b/packages/the-framework/src/dashboard/git-status.SPEC.md @@ -1,5 +1,11 @@ A project's git status for the panel: the current branch, whether there are uncommitted changes, and the pull request linked to that branch. +## User Stories + +- The user sees a project's current branch, whether it has uncommitted changes, and the branch's pull request. +- The user's status row renders without waiting on GitHub; a slow PR lookup arrives late as "not known yet". +- The user viewing an agent's checkout sees that agent's own PR, never a predecessor's from a reused branch name. + ## Flows - Branch and dirtiness are instant local reads; the PR is the slow lookup, served through the cache and allowed to arrive late as "not known yet" rather than holding the row back on every poll. diff --git a/packages/the-framework/src/dashboard/interventions.SPEC.md b/packages/the-framework/src/dashboard/interventions.SPEC.md index 3c2b9cdb7..eb5cdb408 100644 --- a/packages/the-framework/src/dashboard/interventions.SPEC.md +++ b/packages/the-framework/src/dashboard/interventions.SPEC.md @@ -1,9 +1,15 @@ The cross-project "needs you" queue: everything currently waiting on the human, gathered from every registered project. +## User Stories + +- The user sees, in one queue, everything across their projects that only they can move forward. +- The user gets a Discord message when items need them, one line per item. +- The user learns about finished work that was never pushed, instead of it sitting invisible on a branch. + ## Flows - Three kinds of item: an open pull request to review, an agent parked on a question, and a finished agent whose commits were never pushed. -- Hand-opened draft PRs stay off the queue (they are not asking for review yet); an agent's own draft stays on it — the automatic handoff opens drafts precisely so reviewers are not pinged, and the queue is then the only place the work is visible at all. +- A draft PR the user opened by hand stays off the queue — it is not asking for review yet. An agent's own draft stays on: the automatic handoff opens draft PRs precisely so reviewers are not pinged, and the queue is then the only place the work is visible at all. - Unpushed work is surfaced, never acted on, and only a project's few most recent finished agents are inspected — work sitting unpushed for ages is not news. - Forgiving and deduplicated: an unreadable project contributes nothing, and the same repo registered twice contributes each item once. - Also phrases the queue as a Discord message, one line per item, worded by kind. diff --git a/packages/the-framework/src/dashboard/keyed-watcher.SPEC.md b/packages/the-framework/src/dashboard/keyed-watcher.SPEC.md index 145f8720e..6ff6e14b6 100644 --- a/packages/the-framework/src/dashboard/keyed-watcher.SPEC.md +++ b/packages/the-framework/src/dashboard/keyed-watcher.SPEC.md @@ -1,9 +1,14 @@ The notification engine: a background poll over the registered projects that announces only what newly appeared, so a Discord message fires even when no dashboard is open. +## User Stories + +- The user hears about what newly needs them, or newly happened, without keeping a dashboard open. +- The user is not flooded at daemon start: what already existed by then is never announced. + ## Flows -- The first look only takes a baseline — whatever already existed when the daemon started is never announced; you only hear about what happens while it watches. -- What makes two items "the same" is the caller's decision, so one engine serves both the needs-you queue and the activity feed. +- The first look only takes a baseline — whatever already existed when the daemon started is never announced; the user only hears about what happens while it watches. +- What makes two items "the same" is the caller's decision, so one engine serves both callers: the "needs you" queue (open PRs, parked questions, unpushed work) and the activity feed (agents started and finished). - Forgiving: a failed scan or projection simply announces nothing that cycle. - It owns no timer of its own — the daemon's one clock calls it — so its cadence is declared where every other background job's is. diff --git a/packages/the-framework/src/dashboard/open-in-app.SPEC.md b/packages/the-framework/src/dashboard/open-in-app.SPEC.md index 0e0e1245a..99a534270 100644 --- a/packages/the-framework/src/dashboard/open-in-app.SPEC.md +++ b/packages/the-framework/src/dashboard/open-in-app.SPEC.md @@ -1,9 +1,16 @@ Opens a project in the OS file manager or the user's editor, and detects which known editors are installed so the picker can offer them. +## User Stories + +- The user clicks "open folder" or "open in editor" on a project and it opens on their machine. +- The user's editor picker offers only editors that are actually installed. +- The user with a missing editor command gets a readable failure, never a crash. + ## Flows - Local machine only: the opened path is the project's own registered one, never something the browser sent, and a public host has no local checkout to open anyway. -- The stored editor preference wins, then an environment override, then VS Code; a missing command comes back as a friendly failure, never a crash. +- The stored editor preference wins, then the `$FRAMEWORK_EDITOR` environment override, then VS Code; a missing command comes back as a friendly failure, never a crash. +- Editor detection probes each known launcher on PATH — a pure lookup, nothing is spawned — which is how the picker knows what to offer. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/open-questions.SPEC.md b/packages/the-framework/src/dashboard/open-questions.SPEC.md index 9ffd3ab71..b95a54cec 100644 --- a/packages/the-framework/src/dashboard/open-questions.SPEC.md +++ b/packages/the-framework/src/dashboard/open-questions.SPEC.md @@ -1,8 +1,14 @@ Every agent's open question gathered into one hub, so a human can answer any parked agent from one place instead of hunting through their individual views. +## User Stories + +- The user answers any waiting agent's question from one hub, across every project. +- The user sees each question whole — its options, the recommended pick, whether several may be chosen — not just a title. +- The user sees the agent that has waited on them longest first. + ## Flows -- The full question — options, recommendation, whether several may be picked — is read back from each parked agent's own log, because its summary record only carries the title. +- The full question — options, recommendation, whether several may be picked — is read back from each parked agent's own log, because the agent's status record carries only the question's title. - Longest-waiting first: the agent blocked on its human the longest is the one to unblock first. - A question the log no longer shows open (already answered, or unreadable) is dropped — offering an answer the daemon would refuse is worse than one card fewer. diff --git a/packages/the-framework/src/dashboard/overview.SPEC.md b/packages/the-framework/src/dashboard/overview.SPEC.md index 84b15132a..b6311dc38 100644 --- a/packages/the-framework/src/dashboard/overview.SPEC.md +++ b/packages/the-framework/src/dashboard/overview.SPEC.md @@ -1,9 +1,15 @@ The cross-project glance: what the agent is working on right now, how much is queued, which projects and agents were recently active, and which tickets are hot. +## User Stories + +- The user glances at one page and sees every live agent across their projects, most recently updated first. +- The user sees which tickets are hot right now — being worked on, queued for the AI, or flagged high priority — without reading any backlog. +- The user reaches every project's ticket list from one cross-project page, including a project with no tickets yet. + ## Flows -- Active agents come from each project's live records — every concurrent one — most recently updated first; recent agents pool every project's history into one capped, newest-first rail. -- Hot tickets sort into three lanes with strict precedence: being worked on (a live agent is implementing it — hard evidence — or it has a plan), sitting in the AI queue, or merely flagged high priority; everything else stays off the card, which is a shortlist, not the backlog. +- Active agents come from each project's live records — every concurrent one — most recently updated first. Recent agents pool every project's history into one capped, newest-first rail. +- Hot tickets sort into three lanes: being worked on (a live agent is implementing it — hard evidence — or it has a plan), sitting in the AI queue (an open entry of the project's `TODO_AGENTS.md` backlog links to it), or merely flagged high priority. Precedence is strict, in that order. Everything else stays off the card, which is a shortlist, not the backlog. - "High priority" follows the ticket format's 0-10 scale (7 and up). - The cross-project tickets page keeps one list per project, present even when empty, so importing stays reachable there. - Forgiving throughout: an unreadable project contributes nothing. diff --git a/packages/the-framework/src/dashboard/projects.SPEC.md b/packages/the-framework/src/dashboard/projects.SPEC.md index 82adc8eb3..b8466a0a7 100644 --- a/packages/the-framework/src/dashboard/projects.SPEC.md +++ b/packages/the-framework/src/dashboard/projects.SPEC.md @@ -1,9 +1,15 @@ -Turns the registered-project list into what the dashboard shows per project: its name, whether it is still set up, when it was last active, and the agent defaults its repo commits. +Turns the registered-project list into what the dashboard shows per project: its name, whether it is still set up, when it was last active, and the agent defaults its repo commits (`the-framework.yml`). + +## User Stories + +- The user sees each registered project with its name, whether it is still set up, and when it was last active. +- The user sees the defaults an agent in a repo will run under; an edit to the repo's committed config shows up without a restart. ## Flows - Last activity is the newest timestamp across the project's agents. -- One provider, the real registry: with one dashboard host there is no per-agent scope to substitute and no public host to blank out. +- One provider, backed by the real registry, serves every read: a project id resolves to its registered path before the per-project read runs. +- The repo's committed defaults are read fresh on every summary, which is what keeps the launcher current after an edit. - Forgiving: a failed read shows as an inactive project with no activity, never a crash. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/queue.SPEC.md b/packages/the-framework/src/dashboard/queue.SPEC.md index 49f8dd70b..07213b112 100644 --- a/packages/the-framework/src/dashboard/queue.SPEC.md +++ b/packages/the-framework/src/dashboard/queue.SPEC.md @@ -1,5 +1,10 @@ The cross-project view of the AI work queue: every project's open TODO entries rolled up in one place, most-loaded project first. +## User Stories + +- The user sees how much queued AI work each project has, heaviest first. +- The user can trust the count: the card counts exactly what the automatic sweep would drain. + ## Flows - What counts as an entry deliberately matches the sweep that drains the queue — otherwise the card could say "nothing queued" while the sweep works the same file. diff --git a/packages/the-framework/src/dashboard/quota.SPEC.md b/packages/the-framework/src/dashboard/quota.SPEC.md index f9e9572b9..4a471e490 100644 --- a/packages/the-framework/src/dashboard/quota.SPEC.md +++ b/packages/the-framework/src/dashboard/quota.SPEC.md @@ -1,10 +1,16 @@ -Feeds the dashboard's usage panel: the account's quota windows and where they stand against the spending boundary. +Feeds the dashboard's usage panel: the account's quota windows and where they stand against the spending boundary (the line past which unattended work stands down). + +## User Stories + +- The user sees where the account's quota stands at any moment, agent running or not. +- The user moves the spend-limit slider and both the panel and automation follow, with no restart. +- The user can tell a stale or missing reading from real zero usage. ## Flows - The daemon polls for the dashboard's whole life, not just while an agent is up — the panel must answer while nothing is running. - A failed reading never blanks the panel: the last good reading is kept and marked stale, and "no reading at all" is reported as such rather than as zero usage. -- The boundary is recomputed on every read (it moves with the clock), and the user's spend-limit slider is read fresh each time so moving it needs no restart — one source, so the bar the user sees and the line automation obeys cannot disagree. +- The boundary is recomputed on every read, because it moves with the clock. The user's spend-limit slider is read fresh each time, so moving it needs no restart. One source feeds both, so the bar the user sees and the line automation obeys cannot disagree. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/relay-endpoints.SPEC.md b/packages/the-framework/src/dashboard/relay-endpoints.SPEC.md index 00354dcf2..78470e96f 100644 --- a/packages/the-framework/src/dashboard/relay-endpoints.SPEC.md +++ b/packages/the-framework/src/dashboard/relay-endpoints.SPEC.md @@ -1,11 +1,17 @@ The device side of running an agent on another machine: the few endpoints a daemon exposes so a trusted peer daemon can start an agent here, watch its events, and make agent-scoped calls. +## User Stories + +- The user starts an agent on this machine from another machine's dashboard and watches it there, live. +- The user's device list on the other machine shows whether this machine is reachable. +- The user's device is not open to strangers: every relay call needs the device's token. + ## Flows -- Everything sits behind the shared-token guard, so a caller without the device's token is refused before reaching any of this. -- A reachability ping answers even when the relay is not wired and starts nothing — it only proves "I'm here and your token works", which is what the device list's status dots poll. +- Everything sits behind the device's shared token — the one guard fronting every route of a network-reachable daemon — so a caller without it is refused before reaching any of this. +- The online dot the user sees is a reachability ping here: it answers even when the relay is not wired and starts nothing — it only proves "I'm here and your token works". - A relayed start strips any nested relay target, so an agent can never be relayed onward to a third machine. -- Agent-scoped calls go through one channel that admits only named, whitelisted operations; events stream back as a plain line-by-line feed until the agent ends or the caller hangs up. +- Agent-scoped calls go through one channel that admits only named, whitelisted operations. Events stream back as a plain line-by-line feed until the agent ends or the caller hangs up. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/remote-run.SPEC.md b/packages/the-framework/src/dashboard/remote-run.SPEC.md index 01dfa35b0..a14698f7f 100644 --- a/packages/the-framework/src/dashboard/remote-run.SPEC.md +++ b/packages/the-framework/src/dashboard/remote-run.SPEC.md @@ -1,10 +1,16 @@ The local half of running an agent on a saved device: this daemon — never the browser — drives the remote daemon and streams the agent back so it looks and behaves like a local one. +## User Stories + +- The user starts an agent on a saved device and watches and steers it in the dashboard like a local one. +- The user sees a live online/offline dot for each saved device. +- The user reloads the dashboard and the remote agent is still in the list, re-openable. + ## Flows - The device's token stays between the two daemons and only ever in memory; the browser watches the agent over its normal same-origin channel, so nothing crosses origins in the browser and the token never reaches a page. - Short health pings are how the device list's online/offline dots know what is reachable. -- A relayed agent keeps a local stand-in record, updated from the device's events, so it shows in the agent list and survives a dashboard reload; the device address outlives the event stream because reads after it ends, push, and PR still have to reach the device. +- A relayed agent keeps a local stand-in record, updated from the device's events, so it shows in the agent list and survives a dashboard reload. The device's address outlives the event stream: reads after the agent ends, push, and opening a PR still have to reach the device. - Failures answer like local ones: an unreachable device is a plain error result or an empty read, and a rotated token ends the stream as a normal finish, not a lost connection. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/rpc-serve.SPEC.md b/packages/the-framework/src/dashboard/rpc-serve.SPEC.md index 2f029717a..ac6d3f485 100644 --- a/packages/the-framework/src/dashboard/rpc-serve.SPEC.md +++ b/packages/the-framework/src/dashboard/rpc-serve.SPEC.md @@ -1,12 +1,17 @@ Mounts the dashboard's RPC surface on the daemon's server and keeps other websites out of it. +## User Stories + +- The user's own dashboard page is the only website that can start or steer agents on their machine. +- The user watching an agent's live feed can tell "the agent finished" from "the connection dropped". + ## Flows -- Two browser guards: calls from another website's page are refused (it must not start or steer agents on the user's machine), and so are requests whose named host betrays the DNS trick that makes a hostile page look same-origin. -- The RPCs run inside the daemon's own process and reach its capabilities through wiring set once at start-up, which carries all of them — there is no second host to wire a different subset, and nothing about the wiring varies per caller. +- Two browser guards keep other websites out. A call from another website's page is refused: a page the user merely visited must not start or steer agents on their machine. Refused too is a request whose named host betrays the DNS trick that makes a hostile page look same-origin — the request carries the name the browser was asked for, not the address it resolved to, so a rebound name gives itself away. +- The RPCs run inside the daemon's own process, through wiring set once at start-up. That wiring carries every capability: there is no second host wiring a different subset, and nothing varies per caller. - Calls are addressed by name over plain HTTP, and the live feed is Server-Sent Events. - The feed's response simply ending is how a viewer tells "the server is done" from "the connection dropped" — the first is a finished agent, the second is worth retrying and worth saying out loud. -- A malformed request is answered, and so is one whose RPC throws: a failing call is a failing call, never a dead daemon. A name that is not an RPC is one of those answers, including the names every object carries whether anyone registered them or not. +- A malformed request is answered, and so is one whose RPC throws: a failing call is a failing call, never a dead daemon. A name that is not an RPC is answered with "no such RPC" — including the built-in names every object carries (`constructor`, `toString`), which are not RPCs no matter that every object technically has them. ## Rationales diff --git a/packages/the-framework/src/dashboard/server.SPEC.md b/packages/the-framework/src/dashboard/server.SPEC.md index 7697df499..a5b8c5721 100644 --- a/packages/the-framework/src/dashboard/server.SPEC.md +++ b/packages/the-framework/src/dashboard/server.SPEC.md @@ -1,11 +1,17 @@ The dashboard's front door: one small web server that serves the app, mounts its RPC surface, and routes the special channels — browser preview, device relay, cloud-session bridge. +## User Stories + +- The user runs the dashboard with defaults and it is reachable from this machine only. +- The user binds the dashboard to a network address and shares its tokened link; a visitor without the token is refused on every route — nothing to see, nothing to do. +- The user follows the tokened link once; after one hop the token is out of the URL and a cookie keeps them in. + ## Flows -- Bound to this machine only by default; on a reachable address one shared token guards every route, because a daemon that spawns processes on an open port is remote code execution. A valid token in a link becomes a cookie and leaves the URL after one hop. -- The cloud-session bridge is the only route in front of that guard: it is meant to be called from another origin, so it authenticates with its own token instead. +- The default bind is this machine only. When the user binds a reachable address, one shared token guards every route, because a daemon that spawns processes on an open port is remote code execution. A valid token in a link becomes a cookie and leaves the URL after one hop. +- The cloud-session bridge is the only route in front of that guard: it is meant to be called from another origin (the user's browser extension reporting a cloud session's question), so it authenticates with its own token instead. - One host, wiring everything: there is no second dashboard to serve a single agent on its own port, so nothing here is optional and no call has to ask what this server can do. -- A broken install with no built app answers "not installed" everywhere rather than standing up half a dashboard, and malformed requests are answered, never allowed to crash the process. +- A broken install with no built app answers "not installed" everywhere rather than standing up half a dashboard. Malformed requests are answered, never allowed to crash the process. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/tickets.SPEC.md b/packages/the-framework/src/dashboard/tickets.SPEC.md index 492d745ad..7d8291718 100644 --- a/packages/the-framework/src/dashboard/tickets.SPEC.md +++ b/packages/the-framework/src/dashboard/tickets.SPEC.md @@ -1,11 +1,17 @@ Reads a project's ticket backlog for the dashboard, so what the agent plans from is visible without opening the repo. +## User Stories + +- The user browses a project's ticket backlog on the dashboard, newest first, without opening the repo. +- The user sees, per ticket, whether a plan exists and whether an agent holds a claim on it — and whose claim they would be releasing. +- The user's tickets from before the ticket format still show up; nothing is dropped over a missing heading or key. + ## Flows -- A ticket is one file; its plan and its claim are sibling files folded into it, never rows of their own — "planned" means a plan exists, "locked" means an agent holds a claim (an unreadable claim still locks; the holder's name is only display sugar). +- A ticket is one file. Its plan and its claim are sibling files folded into it, never rows of their own: "planned" means a plan file exists, "locked" means an agent holds a claim file. An unreadable claim still locks — the file's existence is the claim; the holder's name is only display sugar. - Deliberately tolerant of tickets predating the format: a missing heading, summary, or key falls back rather than dropping the ticket, and the plan's effort/uncertainty values ride along when named. -- A ticket's date is the one its filename carries, falling back to file time, so mere edits do not reshuffle the newest-first list. -- Ticket names taken from the browser must be bare filenames, so they can never address another directory; the last GitHub-import stamp reads any malformed state as "not known" rather than failing. +- A ticket's date is the one its filename carries, so editing a ticket does not reshuffle the newest-first list; only a ticket predating the dated-filename format falls back to file time and moves when edited. +- A ticket name taken from the browser must be a bare filename, so it can never address another directory. The last GitHub-import stamp reads any malformed state as "not known" rather than failing. ## Before modifying/creating SPEC.md files From 6d789cb7eaf742a649b1c2db0f50af00450e81d6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 09:15:17 +0000 Subject: [PATCH 13/16] Style pass: re-voice the daemon dashboard reads (A-G) and bridge to the user-story axis Handoff, activity, bridge, and file-read specs gain the stories of what the user sees (auto draft PR, question cards, hover diffs, docs rail); flows open from those surfaces; foreign coinages glossed; the bridge degradation claim scoped to the routes that actually degrade. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/src/dashboard/SPEC.md | 18 +++++++-------- .../src/dashboard/activity.SPEC.md | 10 ++++++--- .../src/dashboard/agent-handoff.SPEC.md | 22 +++++++++++++------ .../src/dashboard/bridge-endpoints.SPEC.md | 12 ++++++++-- .../src/dashboard/bridge-sessions.SPEC.md | 4 ++++ .../src/dashboard/bridge-store.SPEC.md | 13 +++++++---- .../src/dashboard/browser-proxy.SPEC.md | 8 +++++-- .../the-framework/src/dashboard/cache.SPEC.md | 4 ++-- .../src/dashboard/dashboard.SPEC.md | 9 ++++++-- .../the-framework/src/dashboard/docs.SPEC.md | 6 ++++- .../src/dashboard/file-diff.SPEC.md | 9 ++++++-- .../src/dashboard/file-read.SPEC.md | 6 ++++- 12 files changed, 86 insertions(+), 35 deletions(-) diff --git a/packages/the-framework/src/dashboard/SPEC.md b/packages/the-framework/src/dashboard/SPEC.md index f24ea0fba..89c555e14 100644 --- a/packages/the-framework/src/dashboard/SPEC.md +++ b/packages/the-framework/src/dashboard/SPEC.md @@ -18,28 +18,28 @@ The daemon's serving and projection layer: the HTTP host behind the dashboard, t - Configuration arms auto-merge; only the agent's ready signal authorizes it, and a withheld merge is recorded with its reason. - Two Discord watchers post only what is genuinely new: activity, and what needs a human. - A saved device's agents are started and streamed through the local daemon, never the browser. -- A browser extension posts parked claude.ai questions in; picked answers queue back out. +- A browser extension posts parked claude.ai questions in; answers the user picks queue back out. - The daemon relays an agent's live browser screencast and clicks, on a port from the agent's own record. ## Flows -**The read models.** Everything the dashboard shows is assembled here from what is already on disk — agent logs, tickets, the queue, git state, GitHub state. Reads are forgiving: whatever fails yields an empty result, never a crash at the view. +**The read models.** Everything the dashboard shows the user is assembled here from what is already on disk — agent logs, tickets, the queue, git state, GitHub state. A read that fails yields an empty result, never a crash at the view. **Serving.** Serving is guarded by where the daemon is bound: on localhost, browser calls must come from the dashboard's own origin; on a reachable address, everything demands a shared token, because a daemon that spawns processes on an open port is remote code execution. -**The cache.** Expensive questions (a GitHub lookup costs many times a git read) go through a cache that asks once for all concurrent callers, serves the last good answer while refreshing, and answers "pending" — not "failed" — when a cold ask exceeds its time budget, so a caller that must not act on a half-answer can hold off. A failure never overwrites the last good value. +**The cache.** Expensive questions (a GitHub lookup costs many times a git read) go through a cache that asks once for all concurrent callers, serves the last good answer while refreshing, and answers "pending" — not "failed" — when a cold ask exceeds its time budget. "Pending" lets a caller that must not act on a half-answer hold off, and a failure never overwrites the last good value. -**The handoff.** When an agent settles cleanly, decide whether it is empty (no commits, or only bookkeeping changes) — empty agents are never published. Otherwise commit what it left uncommitted, push the branch, open the PR. The PR number is then recorded on the agent, so every surface reads the same integer instead of re-deriving it; its *state* is still read live, because that changes without the agent doing anything. A PR opened after the process is gone is recorded too, by patching the archive. +**The handoff.** When an agent settles cleanly, its work is measured first: an agent that produced nothing (no commits, or changes only to the framework's own records) is never published. Otherwise what it left uncommitted is committed, the branch is pushed, and the PR is opened. The PR number is then recorded on the agent, so every surface reads the same integer instead of re-deriving it; its *state* is still read live, because that changes without the agent doing anything. A PR opened after the process is gone is recorded too, by patching the agent's archived record. -**Merging.** Arming and authorizing are separate: configuration arms auto-merge, and only the agent's ready-for-merge signal plus an empty backlog of its own authorizes it — an armed-but-unauthorized merge is recorded as withheld, with the reason. On a repo without native auto-merge the PR is handed to the CI watch to merge once checks pass, and the merge outcome lands on the agent's record so every surface can say what happened. +**Merging.** Arming and authorizing are separate: configuration arms auto-merge, and only the agent's ready-for-merge signal plus an empty TODO backlog of the agent's own authorizes it. An armed-but-unauthorized merge is recorded as withheld, with the reason. On a repo without native auto-merge the PR is handed to the daemon's CI watch to merge once checks pass, and the merge outcome lands on the agent's record so every surface can say what happened. -**Watchers.** Two Discord notification watchers — activity (agents started and finished) and interventions (an open PR to review, an agent parked on a question, a finished agent whose commits were never pushed) — each remembering what it already announced, so only new items post. Outbound only: Discord is a way for the product to reach the user, never a way to steer it. The first look only takes a baseline, and the cursor keeps advancing while notifications are off: turning them on starts from now instead of flushing a backlog. The open-questions hub gathers every parked question across all agents, with its full options read back from each one's own log. +**Watchers.** Two Discord notification watchers — activity (agents started and finished) and interventions (an open PR to review, an agent parked on a question, a finished agent whose commits were never pushed) — each remembering what it already announced, so only new items post. Outbound only: Discord is a way for the product to reach the user, never a way to steer it. The first look only takes a baseline, and the record of what has been seen keeps advancing while notifications are off: turning them on starts from now instead of flushing a backlog. The open-questions hub gathers every parked question across all agents, with its full options read back from each one's own log. -**Remote devices.** The local daemon — never the browser — talks to a saved device's daemon: it starts the agent there and streams the events back over the browser's normal same-origin channel; agent-scoped calls are forwarded the same way, with the device side accepting only an allowlisted set (reads and steering — starting and deleting are deliberately not remotable this way). An unreachable device answers like any failed local read: empty. +**Remote devices.** When the user starts an agent on a saved device, the local daemon — never the browser — talks to that device's daemon: it starts the agent there and streams the events back over the browser's normal same-origin channel. Agent-scoped calls are forwarded the same way, with the device side accepting only an allowlisted set (reads and steering — starting and deleting are deliberately not remotable this way). An unreachable device answers like any failed local read: empty. -**The cloud-session bridge.** A browser extension inside the user's own claude.ai tab posts parked questions in; picked answers queue back out for the extension to type into the cloud composer. The payload is tiny and fully validated — no paths, no commands, no free text — so the worst a stolen token buys is a bogus question card. +**The cloud-session bridge.** A browser extension inside the user's own claude.ai tab posts parked questions in; answers the user picks queue back out for the extension to type into the cloud composer. The payload is tiny and fully validated — no paths, no commands, no free text — so the worst a stolen token buys is a bogus question card. -**The live browser.** The dashboard cannot reach an agent's Chrome directly (wrong origin), so the daemon proxies the screencast and the clicks — and the port comes from the agent's own record, never from the client, which is what keeps the proxy from being an open relay into anything else on the machine. +**The live browser.** The dashboard cannot reach an agent's Chrome directly (wrong origin), so the daemon proxies the screencast and the clicks. The port comes from the agent's own record, never from the client — which is what keeps the proxy from being an open relay into anything else on the machine. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/activity.SPEC.md b/packages/the-framework/src/dashboard/activity.SPEC.md index 8523cb176..35be33e2f 100644 --- a/packages/the-framework/src/dashboard/activity.SPEC.md +++ b/packages/the-framework/src/dashboard/activity.SPEC.md @@ -1,10 +1,14 @@ -The activity feed: the cross-project stream of agent lifecycle moments — an agent started, an agent finished — that notify without needing the human. +The activity feed: the cross-project stream of agent lifecycle moments — an agent started, an agent finished — that notify the user without needing anything from them. + +## User Stories + +- The user turns on activity notifications and is told when an agent starts and when one finishes, without watching the dashboard. ## Flows - Each project's recent agents contribute one item apiece: "started" while it runs, "finished" once it ends, tagged by how it ended so a stop reads differently from a success. -- A start and a finish are separate events, so an agent notifies at most twice — and one that starts and ends between two looks notifies once, as finished. -- The default-off "for your information" counterpart to the always-on "needs you" interventions queue. +- A start and a finish are separate events, so an agent notifies at most twice — and one that starts and ends between two polls notifies once, as finished. +- Activity is off by default: it is the "for your information" counterpart to the always-on interventions feed, the list of what needs the user. - Several updates post to Discord as one message; a project whose history cannot be read contributes nothing. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/agent-handoff.SPEC.md b/packages/the-framework/src/dashboard/agent-handoff.SPEC.md index 6aa90fbc8..ae1c5318d 100644 --- a/packages/the-framework/src/dashboard/agent-handoff.SPEC.md +++ b/packages/the-framework/src/dashboard/agent-handoff.SPEC.md @@ -1,15 +1,23 @@ -How a finished agent's work is handed back to the human: measure what its branch holds, push it, open a pull request, and decide whether it may merge. +Hands a finished agent's work back to the user: measures what its branch holds, pushes it, opens a pull request, and decides whether it may merge. + +## User Stories + +- The user finds a settled agent's branch pushed and a draft pull request opened for it, without pressing anything. +- The user is told when an agent produced nothing; an empty agent is never published. +- The user pushes, opens the PR, or merges by hand from the handoff panel — the merge button lands the draft an automatic merge withheld. +- The user arms an automatic merge in configuration, and it still waits for the agent's own ready signal. +- The user sees a cloud agent's pull request even though its branch never existed on this machine. ## Flows -- Branch-addressed: an agent reads the same whether or not its checkout still exists, and a locally-gone branch still reports its PR — a hands-off cloud agent only ever pushed remotely. -- An agent that produced nothing — no commits, or only the framework's own bookkeeping — is said so and never published. -- Push and a draft PR are armed by default; drafts keep the automatic path out of reviewers' inboxes, and uncommitted leftovers are swept into a commit first (guarded so only the agent's own checkout and branch are ever committed). +- The read is addressed by branch, not by checkout: an agent reads the same whether or not its checkout still exists. A branch gone locally still reports its pull request — a hands-off cloud agent only ever pushed to the remote. +- An agent that produced nothing — no commits, or changes only to the framework's own records — is reported as empty and never published. +- Push and a draft PR are armed by default; drafts keep the automatic path out of reviewers' inboxes. Whatever the agent left uncommitted is swept into a commit first, guarded so only the agent's own checkout and branch are ever committed. - The PR number is recorded on the agent the moment one is opened for it, so every surface reads the same integer instead of re-deriving it. Its *state* is still read live, because that changes without the agent doing anything. -- A pull request opened after the agent's process is gone is recorded too, by patching its archive: it is the same fact, and a surface should not have to know which of the two paths produced it. +- A pull request opened after the agent's process is gone is recorded too, by patching the agent's archived record: it is the same fact, and a surface should not have to know which of the two paths produced it. - A branch that exists only on the remote — a cloud session's own, pushed from a VM this machine never sees — can still get its draft PR opened: there is nothing to push first, the PR request itself is the whole action. -- The branch's own PR history is still consulted for a different question — does this branch already have one — because a branch name pinned by a prompt is reused across agents, so a reused branch never wears an old PR and a branch with one never gets a second. -- Configuration arms an automatic merge; only the agent's declared-done signal plus an empty backlog of its own authorizes it, and a withheld merge still pushes and opens the draft for a human. +- The branch's own PR history is still consulted for a different question: does this branch already have a pull request. A branch name pinned by a prompt is reused across agents, so the history is filtered by the agent's start time — a reused branch never shows an earlier agent's PR, and a branch that already has one never gets a second. +- Configuration arms an automatic merge; it runs only when the agent has signalled ready for merge and the agent's own TODO backlog is empty. A withheld merge still pushes and opens the draft PR for the user. ## Rationales diff --git a/packages/the-framework/src/dashboard/bridge-endpoints.SPEC.md b/packages/the-framework/src/dashboard/bridge-endpoints.SPEC.md index e29c04a8e..b73bc8d1d 100644 --- a/packages/the-framework/src/dashboard/bridge-endpoints.SPEC.md +++ b/packages/the-framework/src/dashboard/bridge-endpoints.SPEC.md @@ -1,12 +1,20 @@ The daemon's doorway for the browser extension in the user's own claude.ai tab: questions a cloud session is parked on (and what it said) come in, and answers picked in the dashboard go back out. +## User Stories + +- The user sees the question a cloud agent is parked on as an answerable card in the dashboard, reported by the extension in their own claude.ai tab. +- The user reads what the cloud session has said so far: its transcript follows along in the agent's view. +- The user's picked answer is fetched by the extension and typed into claude.ai, and the dashboard shows how the delivery went. +- The user is told when the extension and the daemon disagree on version, and how to fix it. + ## Flows - The one surface meant to be reached from another origin, so every call must present a shared secret, checked before anything else is read. - Payloads are tiny and validated field by field — no paths, no commands, no free text — so a stolen secret buys at worst a bogus question card. +- What the session said comes in as transcript entries and feeds the transcript in the agent's view; a batch with one bad entry is refused whole, so a gap never masquerades as a message still on its way. - The extension can also ask which cloud sessions deserve a tab, fetch a session's queued answer, and report how delivering it went. -- With the feature off, everything answers "not found"; a daemon missing one piece degrades to "nothing" rather than an error. -- The extension states its version on every call, and a daemon expecting another refuses every route — naming both versions and the way out — because a version-skewed extension half-works in ways that read as framework bugs; the expected version moves in lockstep with the extension's manifest. +- With the feature off, everything answers "not found". A daemon with no session list or no queued-answer source wired answers "nothing" — an empty list, a null — rather than an error. +- The extension states its version on every call, and a daemon expecting another refuses every route, naming both versions and the way out. A version-skewed extension half-works in ways that read as framework bugs, so there is no degraded mode; the expected version moves in lockstep with the extension's manifest. ## Rationales diff --git a/packages/the-framework/src/dashboard/bridge-sessions.SPEC.md b/packages/the-framework/src/dashboard/bridge-sessions.SPEC.md index 0a39a1c64..efd0ec146 100644 --- a/packages/the-framework/src/dashboard/bridge-sessions.SPEC.md +++ b/packages/the-framework/src/dashboard/bridge-sessions.SPEC.md @@ -1,5 +1,9 @@ Decides which cloud sessions the browser extension should keep a tab open for: the few most recent web agents that carry a cloud session. +## User Stories + +- The user's few most recent cloud agents each get a claude.ai tab opened for them by the extension, so a parked question reaches the dashboard without anyone watching claude.ai. + ## Rationales - Recency is the whole filter because a web agent's recorded status says nothing — every one reads as finished the moment it hands off to the cloud — so "recent, and not many" is the honest rule. diff --git a/packages/the-framework/src/dashboard/bridge-store.SPEC.md b/packages/the-framework/src/dashboard/bridge-store.SPEC.md index 63a76d1e5..51b9fae59 100644 --- a/packages/the-framework/src/dashboard/bridge-store.SPEC.md +++ b/packages/the-framework/src/dashboard/bridge-store.SPEC.md @@ -1,12 +1,17 @@ -The daemon's memory of the cloud-session bridge: each session's parked question and transcript, and the dashboard-picked answer on its way back. +The daemon's memory of the cloud-session bridge: each session's parked question and transcript, and the answer the user picked on its way back. + +## User Stories + +- The user sees the question a cloud session is parked on right now, never one the session already moved past. +- The user's answer resolves the question once delivered — and only a label the question itself offered can ever be queued. ## Flows - One parked question per session, replaced when the session moves on; a new question also discards any undelivered answer to the old one, so a stale pick is never typed into a fresh question. -- An answer must be a label the question itself offered; once delivered it resolves the question, and re-reports of that question are ignored so it cannot resurface right after being answered. A failed delivery keeps the question for a retry. +- An answer must be a label the question itself offered. Once delivered it resolves the question, and re-reports of that question are ignored, so it cannot resurface right after being answered. A failed delivery keeps the question for a retry. - Acknowledgements name the answer they are about, so a stale one from a dead tab cannot resolve a newer answer. -- Transcript entries are kept one per position, bounded — the page is re-read constantly, so the same message arrives many times. -- Even refused contacts are remembered, telling a misconfigured extension apart from an absent one. +- Transcript entries are kept one per position, and bounded. The page is re-read constantly, so the same message arrives many times: each arrival replaces the copy at its position, which is also how a message still being streamed fills in. +- Even refused contacts are remembered, so the dashboard can tell the user a misconfigured extension apart from an absent one — both otherwise leave no question behind. ## Rationales diff --git a/packages/the-framework/src/dashboard/browser-proxy.SPEC.md b/packages/the-framework/src/dashboard/browser-proxy.SPEC.md index 582970258..b0ca26ccf 100644 --- a/packages/the-framework/src/dashboard/browser-proxy.SPEC.md +++ b/packages/the-framework/src/dashboard/browser-proxy.SPEC.md @@ -1,9 +1,13 @@ -Lets the dashboard watch and steer an agent's live browser by relaying the screen stream and the clicks through the daemon. +Lets the user watch and steer an agent's live browser from the dashboard, by relaying the screen stream and the clicks through the daemon. + +## User Stories + +- The user watches an agent's live browser inside the dashboard pane and clicks and types into it. ## Flows - The dashboard cannot reach an agent's browser directly (wrong origin), so the pane talks to the daemon and the daemon talks to the agent's own preview channel on the same machine. -- The client never names the port: it comes from the agent's own live record, so the relay cannot be pointed at anything else on the machine, and a finished agent's port is never reused. +- The client never names the port: it comes from the agent's own live record, so the relay cannot be pointed at anything else on the machine. A finished agent's recorded port is never followed — by then the OS may have handed that number to anything. - Frames stream straight through, stop the moment the viewer leaves, and are never cached; an agent with no preview answers "not found", one that just died answers with an error instead of hanging the pane. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/cache.SPEC.md b/packages/the-framework/src/dashboard/cache.SPEC.md index e543e50c1..5f3f98e02 100644 --- a/packages/the-framework/src/dashboard/cache.SPEC.md +++ b/packages/the-framework/src/dashboard/cache.SPEC.md @@ -3,8 +3,8 @@ A read-through cache for the dashboard's slow questions — mostly GitHub ones, ## Flows - Concurrent asks for the same thing share one lookup. -- A known answer is served instantly and refreshed behind the scenes once it ages; a failed refresh keeps the last good answer rather than dropping it. -- A first-ever ask waits only a moment before answering "pending" — meaning not known yet, not "no" — so a caller that must not act on a half-answer can hold off, and a slow lookup delays one panel's detail rather than the whole page. +- A known answer is served instantly and refreshed behind the scenes once it ages; a failed refresh keeps the last good answer, so a panel shows what it last knew rather than blanking on a hiccup. +- A first-ever ask waits only a moment before answering "pending", which means not known yet, never "no". A caller that must not act on a half-answer holds off, and a slow lookup delays one panel's detail rather than the whole page. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/dashboard.SPEC.md b/packages/the-framework/src/dashboard/dashboard.SPEC.md index 139c1aca6..7722a2f2a 100644 --- a/packages/the-framework/src/dashboard/dashboard.SPEC.md +++ b/packages/the-framework/src/dashboard/dashboard.SPEC.md @@ -1,13 +1,18 @@ Assembles the Overview page: the cross-project, at-a-glance rollup of what is running and how much work is waiting. +## User Stories + +- The user opens the Overview and sees at a glance which agents are working right now and how much queued work is waiting, across every project. +- The user's onboarding checklist points at the project they touched last. + ## Flows -- What a reader actually asks of it, and nothing else: how many projects there are and how much work is waiting, which agents are going right now, the per-project queue, and which projects have tickets — ordered most-recently-active first, because the onboarding checklist acts on the head of that list. +- What the user actually asks of the page, and nothing else: how many projects there are, how much work is waiting, which agents are going right now, the per-project queue, and which projects have tickets. Projects are ordered most-recently-active first, because the onboarding checklist acts on the head of that list. - A pure projection of what is already on disk; a project whose records cannot be read simply contributes nothing. ## Rationales -- The payload is pinned to what its readers ask for: a rollup over past agents costs a fan-out over every project's whole archive on every poll, and what a payload costs is a reason to keep it honest about who reads it. +- The payload is pinned to what the page reads: a rollup over past agents would cost reading every project's whole archive on every poll, and a cost like that is only paid for numbers somebody actually looks at. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/docs.SPEC.md b/packages/the-framework/src/dashboard/docs.SPEC.md index 42340f0e2..402ec29b9 100644 --- a/packages/the-framework/src/dashboard/docs.SPEC.md +++ b/packages/the-framework/src/dashboard/docs.SPEC.md @@ -1,4 +1,8 @@ -Surfaces the plan documents at a project's root and the backlog off the data branch, so the human can read them in the dashboard beside the agent. +Surfaces the plan documents at a project's root and the backlog off the framework's data branch (`tf-data`), so the user can read them in the dashboard beside the agent. + +## User Stories + +- The user reads the agents' plan and backlog documents in the dashboard's docs rail, beside the agent they belong to. ## Flows diff --git a/packages/the-framework/src/dashboard/file-diff.SPEC.md b/packages/the-framework/src/dashboard/file-diff.SPEC.md index 8ffcf0ef9..7e855142e 100644 --- a/packages/the-framework/src/dashboard/file-diff.SPEC.md +++ b/packages/the-framework/src/dashboard/file-diff.SPEC.md @@ -1,11 +1,16 @@ Shows what actually changed in a checkout's files: one file's diff for the tree's hover card, and every changed file's line counts for the agent's Changes list. +## User Stories + +- The user hovers a changed file in the tree and reads its diff without leaving the dashboard. +- The user's Changes list names every file the agent touched, with added and removed line counts. + ## Flows - Tracked files diff against the last commit, so a change the agent already staged still shows; an untracked file, having nothing to diff against, renders as all-added from its contents. - A binary change says so instead of dumping bytes, and a long patch is cut for display and says it was cut. -- The whole Changes list costs two git reads however many files changed, and stays sorted so a live agent does not reshuffle it. -- Paths come from the client, so every one goes through the shared safety guard before anything is read. +- The whole Changes list costs two git reads however many files changed, and stays sorted so it does not reshuffle under the user while a live agent edits. +- Paths come from the client, so every one goes through the shared path guard (repo-relative only, no traversal) before anything is read. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/src/dashboard/file-read.SPEC.md b/packages/the-framework/src/dashboard/file-read.SPEC.md index 32e76b2b4..99d3eb508 100644 --- a/packages/the-framework/src/dashboard/file-read.SPEC.md +++ b/packages/the-framework/src/dashboard/file-read.SPEC.md @@ -1,9 +1,13 @@ The safe way the dashboard reads one file out of a checkout, used by the hover card to preview an unchanged file. +## User Stories + +- The user hovers an unchanged file and previews the agent's own copy of it — the worktree's, not the project root's. + ## Flows - One guard for every client-supplied path: repo-relative only, no traversal, no absolute paths, and never into git's own folder, where credentials live. -- Confinement is real, not textual: links are resolved before checking the file sits inside the checkout, so a link pointing outside is refused. +- Confinement is real, not textual: symbolic links are resolved before checking the file sits inside the checkout, so a link pointing outside is refused. - Previews are cut at a fixed length, binary files say so instead of rendering bytes, and anything unreadable is simply nothing. ## Before modifying/creating SPEC.md files From 1f4a81514b221aa724fc52fa43cfbc8ffac5f33a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 09:15:46 +0000 Subject: [PATCH 14/16] Style pass: re-voice the dashboard shell, lib, and design specs The dashboard root's stories become single-claim sentences; App gains its stories; lib hooks name the surfaces they feed (banner, pill, transcript cards, Stop button); coined terms replaced with plain words; dense quota and filter bullets split one idea per sentence. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- packages/the-framework/dashboard/App.SPEC.md | 12 +++++++++--- packages/the-framework/dashboard/SPEC.md | 14 ++++++++------ packages/the-framework/dashboard/design/SPEC.md | 4 ++-- .../dashboard/design/previews.SPEC.md | 2 +- packages/the-framework/dashboard/index.SPEC.md | 2 +- packages/the-framework/dashboard/lib/SPEC.md | 4 ++-- .../dashboard/lib/agent-option-rows.SPEC.md | 2 +- .../dashboard/lib/agent-status.SPEC.md | 4 ++-- .../the-framework/dashboard/lib/live-state.SPEC.md | 6 +++--- .../dashboard/lib/preferences.SPEC.md | 7 ++++--- .../the-framework/dashboard/lib/profiles.SPEC.md | 2 +- .../the-framework/dashboard/lib/quota-bar.SPEC.md | 6 +++--- .../dashboard/lib/ticket-filter.SPEC.md | 2 +- .../dashboard/lib/use-agent-handoff.SPEC.md | 2 +- .../dashboard/lib/use-live-events.SPEC.md | 2 +- 15 files changed, 40 insertions(+), 31 deletions(-) diff --git a/packages/the-framework/dashboard/App.SPEC.md b/packages/the-framework/dashboard/App.SPEC.md index f978ffb4c..191e4456b 100644 --- a/packages/the-framework/dashboard/App.SPEC.md +++ b/packages/the-framework/dashboard/App.SPEC.md @@ -1,10 +1,16 @@ The entire dashboard is this one page: it reads the selection from the address, routes the main view accordingly, and owns everything the views share. +## User Stories + +- The user opens every view — the overview, a project home, one agent, tickets, settings — as a plain URL to paste, reload, or bookmark. +- The user watches an agent they just started right away, before the daemon has written its record. +- The user is told when the daemon stops answering, instead of watching a page that froze silently. + ## Flows -- The address is the selection — overview, project home, one agent, settings, tickets and ticket pages — so every view is a link to paste, reload, or bookmark. -- The page owns what the views share: the agent list, the project list (each project carrying what the daemon currently finds wrong with it), project files, the cross-project needs-you queue, and the one live event stream the main view and right rail both read. -- A just-started session shows live before its record exists; with no id known yet, the page follows the output and adopts the running session once it surfaces. +- The address is the selection — overview, project home, one agent, settings, tickets and ticket pages; nothing else remembers which view is open. +- The page owns what the views share: the agent list, the project list (each project carrying what the daemon currently finds wrong with it), project files, the cross-project needs-you queue (an open PR to review, an agent paused on a question, unpushed work), and the one live event stream the main view and right rail both read. +- A just-started agent shows live before its record exists; with no id known yet, the page follows the project's output and adopts the running agent once it surfaces. - Live and finished agents are the same view — only the "live" flag flips when an agent ends. - A daemon that stops answering gets a banner, so a dead backend never looks like a quiet agent. diff --git a/packages/the-framework/dashboard/SPEC.md b/packages/the-framework/dashboard/SPEC.md index 3c9de4a01..9e74adbb2 100644 --- a/packages/the-framework/dashboard/SPEC.md +++ b/packages/the-framework/dashboard/SPEC.md @@ -2,12 +2,14 @@ The dashboard UI: a browser app served by the daemon that renders everything the ## User Stories -- The user starts an agent from the composer — typing a prompt for an attended one, picking a preset for an unattended one — and is warned before spending when something would block the handoff. -- The user watches an agent as a live transcript, answers the questions it parks on right where they happened, chats with it, and stops or resumes it. +- The user starts an agent from the composer: a typed prompt starts an attended build, a picked preset an unattended one. +- The user is warned before an agent is spent when something would block its handoff. +- The user watches an agent as a live transcript and answers the questions it parks on right where they happened. +- The user chats with a live agent, stops it, or resumes a stopped one. - The user reviews an agent's changed files and diffs and hands the work off — push, open a PR, merge. - The user sees on one overview everything that needs them: the quota bar, every unanswered question across all projects, agents working now, the AI queue, routine work, and the hottest tickets. - The user browses every project's tickets in one filterable list, shares the filtered view as a URL, and starts an agent straight from a ticket. -- The user follows an onboarding checklist whose steps tick themselves off real facts, not clicks. +- The user follows an onboarding checklist whose steps are ticked by real facts, not by clicking them. - The user tunes every preference in settings, from appearance to automation and spend. ## Flows — TL;DR @@ -29,13 +31,13 @@ The dashboard UI: a browser app served by the daemon that renders everything the - An agent's events stream live over one channel bound to its own log; everything else polls. A finished agent reads from the archive instead, catching up whenever the live channel outgrew it. - The dashboard is a plain client-side app: one static page the daemon serves for every address, and all the behaviour in the browser — no server rendering, no framework between the page and the app. -**The overview** is ordered by what governs what: the quota bar first (a week-track with pace and projection — the one figure that decides what agents may do next), then everything that needs *you* — the open-questions hub, every agent's unanswered question across all projects, answerable right there in one scrolling view — then the agents working now, the full AI queue of every project (uncollapsed: a plan you cannot read is not a plan), routine work, and the hottest tickets. An onboarding checklist sits on top until dismissed; each step's "done" is derived from a real fact (a registered project, a ticket on disk, a granted permission, stored credentials), so a step cannot be ticked by clicking it and work done outside the dashboard shows up ticked anyway. +**The overview** is ordered by what governs what. The quota bar comes first — a week-track with pace and projection, the one figure that decides what agents may do next. Then everything that needs *you*: the open-questions hub, every agent's unanswered question across all projects, answerable right there in one scrolling view. Then the agents working now, the full AI queue of every project (uncollapsed: a plan you cannot read is not a plan), routine work, and the hottest tickets. An onboarding checklist sits on top until dismissed. Each step's "done" is derived from a real fact — a registered project, a ticket on disk, a granted permission, stored credentials — so a step cannot be ticked by clicking it, and work done outside the dashboard shows up ticked anyway. -**The composer** starts and steers agents. Typing a prompt starts an attended build; picking a preset starts an unattended one. In-editor triggers pull in presets and actions, files, projects, and macro tags; option menus write straight to the user's or project's preferences. Pre-flight checks warn before the agent is spent — a missing or logged-out GitHub CLI, a repo that can't auto-merge. On an agent, the composer is its control: a live one takes messages (options are baked at spawn and hidden), a stopped one offers to resume with reduced options, and the submit slot doubles as Stop while it works. +**The composer** starts and steers agents. Typing a prompt starts an attended build; picking a preset starts an unattended one. In-editor triggers pull in presets and actions, files, projects, and macro tags (the angle-bracket placeholders preset prompts use); option menus write straight to the user's or project's preferences. Pre-flight checks warn before the agent is spent — a missing or logged-out GitHub CLI, a repo that can't auto-merge. On an agent, the composer is its control: a live one takes messages (options are baked at spawn and hidden), a stopped one offers to resume with reduced options, and the submit slot doubles as Stop while it works. **The agent view** is a transcript with the controls inline: its questions render as answerable cards exactly where they happened (resolved ones collapse to a checkmark), and its live browser screencast renders inline too, degrading to a last still when the agent ends. Around the transcript: changed files with diffs, git status, the handoff panel (push, open PR, merge), agent-authored views, docs, and history rails, and an actions menu (stop, open in editor or on GitHub, remove worktree, delete it, copy a resume command). -**Tickets** are the roadmap surface: a cross-project list with client-side faceted filtering (text, priority/effort/uncertainty buckets or ranges, topics, planning stage, project), sorting, and a group-by-project toggle — the whole view mirrored to the URL so it can be shared. Each ticket row leads with a start button that spins up an unattended agent implementing that one ticket, and shows whether a plan exists: a link to a page rendering the plan when it does, a button that starts an agent to write one when it doesn't. Queueing a ticket into the AI queue happens from the ticket's own page. +**Tickets** are the roadmap surface: a cross-project list with client-side faceted filtering (text, priority/effort/uncertainty buckets or ranges, topics, planning stage, project, locally-written-only), sorting, and a group-by-project toggle — the whole view mirrored to the URL so it can be shared. Each ticket row leads with a start button that spins up an unattended agent implementing that one ticket, and shows whether a plan exists: a link to a page rendering the plan when it does, a button that starts an agent to write one when it doesn't. Queueing a ticket into the AI queue happens from the ticket's own page. **Settings** covers appearance, driver and model defaults, the options every new agent starts with, saved devices, notification channels, automation (the idle sweep and the spend slider), and the cloud-session bridge token. diff --git a/packages/the-framework/dashboard/design/SPEC.md b/packages/the-framework/dashboard/design/SPEC.md index fe6986707..f8f5bfb79 100644 --- a/packages/the-framework/dashboard/design/SPEC.md +++ b/packages/the-framework/dashboard/design/SPEC.md @@ -3,9 +3,9 @@ The dashboard's design gallery: static pages that show the design foundations (c ## Flows - Cards render the shipped components themselves against the app's own compiled stylesheet, so what the gallery shows is what the dashboard ships — silent drift between gallery and app is exactly what this exists to catch. -- The few surfaces that cannot render statically (popups that appear only at runtime) are hand-copied and visibly flagged as replicas. +- The few surfaces that cannot render statically — runtime-only popups, or anything needing a live daemon answer — are hand-copied and visibly flagged as replicas. - Cards are pure pages with nothing to click; hover and open states appear as separately rendered instances. -- The build turns the card registry into one page per card, each self-contained and showing light and dark side by side, ready for the design-sync upload. +- The build turns the card registry into one page per card, each self-contained and showing light and dark side by side, ready to be uploaded to the external design-review tool. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/design/previews.SPEC.md b/packages/the-framework/dashboard/design/previews.SPEC.md index d22c40422..23490d786 100644 --- a/packages/the-framework/dashboard/design/previews.SPEC.md +++ b/packages/the-framework/dashboard/design/previews.SPEC.md @@ -1,4 +1,4 @@ -The gallery's card registry — the design foundations (color tokens, status palette, type and radius scales) and the dashboard's building blocks — where every card renders the real shipped component so it cannot drift from the app, and the few that cannot (popups that render nowhere statically) are hand-copied and flagged as replicas on the card itself. +The gallery's card registry: cards for the design foundations (color tokens, status palette, type and radius scales) and for the dashboard's building blocks, each rendering the real shipped component so it cannot drift from the app — and the few that cannot render statically are hand-copied, flagged as replicas on the card itself. ## Rationales diff --git a/packages/the-framework/dashboard/index.SPEC.md b/packages/the-framework/dashboard/index.SPEC.md index c5247007c..9cfbd8665 100644 --- a/packages/the-framework/dashboard/index.SPEC.md +++ b/packages/the-framework/dashboard/index.SPEC.md @@ -3,7 +3,7 @@ The static shell the daemon serves for every address: the root element the app m ## Flows - Served for every path, not just `/`: the app owns routing, so an unknown path is the shell plus a client-side decision, never a 404 from the daemon. -- The tab icon carries its own dark-mode ramp inside the file, because a favicon sits on browser chrome — which follows the OS theme, not the in-app theme choice. +- The tab icon swaps to its dark-mode palette inside the file itself, because a favicon sits on browser chrome — which follows the OS theme, not the in-app theme choice. ## Rationales diff --git a/packages/the-framework/dashboard/lib/SPEC.md b/packages/the-framework/dashboard/lib/SPEC.md index 7c39a64d7..099b5687d 100644 --- a/packages/the-framework/dashboard/lib/SPEC.md +++ b/packages/the-framework/dashboard/lib/SPEC.md @@ -2,11 +2,11 @@ The dashboard's client-side logic layer: the dashboard owns no facts of its own, ## Flows -- Freshness comes two ways: one live feed streams the selected agent's transcript (replay the log, then follow it, never showing less than it already showed), and everything else re-asks the daemon on short intervals. All reads share one guarded pattern — a failure keeps the last answer, a late answer for an abandoned target is dropped, absence is never claimed before the first answer — and a separate heartbeat is what turns "daemon unreachable" into a visible fact instead of silently frozen panels. +- Freshness comes two ways: one live feed streams the selected agent's transcript (replay the log, then follow it, never showing less than it already showed), and everything else re-asks the daemon on short intervals. All reads share one guarded pattern: a failure keeps the last answer, a late answer for an abandoned target is dropped, and absence is never claimed before the first answer. A separate heartbeat turns "daemon unreachable" into a visible banner instead of silently frozen panels. - Anything two surfaces show is computed once and shared so they cannot drift: an agent's options table with its cross-option rules, the one-word status pill, the status color vocabulary, agent and queue-entry labels, event badge wording, the quota week's bar arithmetic, and timestamp formatting that never shows a broken date. - The URL is the selection — which view, project, and agent is open, and the whole tickets view (filters, sort, grouping) — so any state worth looking at is a link you can share, reload, and go Back from. - Preferences live in the daemon and are cached here once for every reader, so a change made on one surface shows on all of them at once; the exception is saved remote devices, whose access tokens are per-browser secrets that never leave the browser except handed along per call. -- Attention plumbing keeps a backgrounded tab honest: browser notifications for new needs-you items and agent activity (sharing the daemon notifier's idea of "new"), the needs-you count folded into the tab title, and the tab icon animating while an agent works. +- Attention plumbing keeps a backgrounded tab honest: browser notifications when something new needs the user or an agent starts or finishes (what counts as "new" is the same rule the daemon's own notifier runs), the count of items needing the user folded into the tab title, and the tab icon animating while an agent works. - Rules that can be pure are pure, wrapped thinly for the pages — so the behavior above is testable without a browser. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/lib/agent-option-rows.SPEC.md b/packages/the-framework/dashboard/lib/agent-option-rows.SPEC.md index d35cb4add..f3262cb7d 100644 --- a/packages/the-framework/dashboard/lib/agent-option-rows.SPEC.md +++ b/packages/the-framework/dashboard/lib/agent-option-rows.SPEC.md @@ -5,7 +5,7 @@ An agent's options as one table with every rule between them already applied, so - A box shows the option's effective value, not the stored one: an option overridden by another reads as off, because off is what the agent will do. - Transparent turns the whole framework off, so it disables every option below it. - Publishing is a strict ladder — push branch, open PR, auto-merge — each rung alive only while the one below is on, which makes "publish nothing" expressible and the contradictory PR-without-push state unreachable. Auto-merge is the one rung off by default: publishing a branch is reversible, landing it is not. -- Three boxes, one stored rung: a row says what ticking it *writes* rather than assuming its own name is a setting, so unticking one lowers the whole ladder instead of leaving a merge armed over a pull request nobody asked for. +- The three publishing boxes store one rung between them: each box writes the rung it means, so unticking one lowers the whole ladder instead of leaving a merge armed over a pull request nobody asked for. - The browser option is offered only under Claude Code, the one agent it is wired to; every disabled row says why. - A finished agent's composer shows the filtered subset of the same table: only the options a resume will actually apply. diff --git a/packages/the-framework/dashboard/lib/agent-status.SPEC.md b/packages/the-framework/dashboard/lib/agent-status.SPEC.md index 5b98c52d4..02540acc9 100644 --- a/packages/the-framework/dashboard/lib/agent-status.SPEC.md +++ b/packages/the-framework/dashboard/lib/agent-status.SPEC.md @@ -1,10 +1,10 @@ -Reduces everything an agent has reported to the single word its status pill shows — one agent, one word. +Reduces everything an agent has reported to the single word its status pill shows — one agent, one word, the same in the session toolbar and on the overview. ## Flows - An agent can hold several facts at once (it can say ready-for-merge and then fail), so a ranking picks the word: how it ended outranks anything it said on the way, because a green "ready for merge" must never describe an agent that then failed or was stopped. - Between a clean end and the report that publishing finished, the pill says "publishing…" — pushing, opening the PR, or merging is what is actually happening then. -- It pulses "building…" only while the agent is live, says nothing until it has said something, and a resume starts the ranking over, so an earlier stop or an earlier publish never sticks to the new leg. +- It pulses "building…" only while the agent is live, and says nothing until the agent has said something. A resume starts the ranking over, so an earlier stop or an earlier publish never sticks to the resumed run. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/lib/live-state.SPEC.md b/packages/the-framework/dashboard/lib/live-state.SPEC.md index 1f9a0c3e1..8ea49babb 100644 --- a/packages/the-framework/dashboard/lib/live-state.SPEC.md +++ b/packages/the-framework/dashboard/lib/live-state.SPEC.md @@ -2,9 +2,9 @@ Answers the live view's questions purely from an agent's event stream — the da ## Flows -- The choice gates the agent is parked on: an answer closes one, the agent's end closes them all, so a dead agent's question stops looking answerable. -- The markdown views the agent has shown, one entry each, updated in place when re-shown. -- Whether the agent is still going, how it ended (clean, crashed, or stopped by you), and whether the agent has settled and now waits on you although its process stays alive as a conversation. +- The questions the agent is parked on — the transcript's answerable cards: an answer closes one, the agent's end closes them all, so a dead agent's question stops looking answerable. +- The markdown views the agent has shown — the right rail's views tab: one entry per view, updated in place when re-shown. +- Whether the agent is still going (which is whether Stop is worth offering), how it ended (clean, crashed, or stopped by you), and whether the agent has settled: done working and waiting on you, while its process stays alive as a conversation. - Whether a cleanly-ended agent is still publishing — its armed handoff has not reported back yet — read off the stream, or off its stored summary for list rows. - Links to a run's external home (its GitHub Actions run, its cloud session), found even by a tab opened mid-run. diff --git a/packages/the-framework/dashboard/lib/preferences.SPEC.md b/packages/the-framework/dashboard/lib/preferences.SPEC.md index 23c387ffd..c9da3b036 100644 --- a/packages/the-framework/dashboard/lib/preferences.SPEC.md +++ b/packages/the-framework/dashboard/lib/preferences.SPEC.md @@ -1,12 +1,13 @@ -The dashboard's settings: your own options with the open project's committed settings file on top, resolved nearest-wins into the one value every control reads. +The dashboard's settings: your own options with the open project's committed settings file on top — where both set a key the project file wins — resolved into the one value every control reads. ## Flows - Owned and persisted by the daemon, so settings follow the installation, not one browser. -- Two tiers, one of them writable: a repo-shaped setting is edited in the repo, so every control writes to the same place and there is no split to get wrong. +- Two tiers, one of them writable: the dashboard writes only your own options — the project's committed file is edited in the repo — so every control writes to the same place and there is no split to get wrong. - A change shows instantly and saves in the background; the write sends only the keys it changed and adopts the daemon's merged answer, so a stale tab can neither revert other people's changes nor keep showing them wrong. - Returning to the tab re-reads both tiers, making edits from another tab or from the repo's settings file on disk visible. -- Each control can also see which tier won its value — a repo-inherited value shows as not yours — and a project's shared, repo-committed custom presets ride along. +- Each control can also see which tier won its value, so a repo-inherited value shows as not yours. +- A project's shared custom presets — committed into the repo, so everyone who clones it sees them — load and save through the same cache. - The theme choice and the notification toggles read through here; the notification defaults are defined framework-side so daemon and dashboard cannot drift, while the theme's default lives here — absent means follow the OS. ## Rationales diff --git a/packages/the-framework/dashboard/lib/profiles.SPEC.md b/packages/the-framework/dashboard/lib/profiles.SPEC.md index 477450c7f..5e249b208 100644 --- a/packages/the-framework/dashboard/lib/profiles.SPEC.md +++ b/packages/the-framework/dashboard/lib/profiles.SPEC.md @@ -4,7 +4,7 @@ The saved daemons this browser can hop between — each remembered as a label, a - Saved only in this browser, on purpose: the token is a per-browser secret and must never land in the daemon's shared registry. - Switching devices is a navigation: the browser goes to the other daemon's address carrying the token once (plus any half-typed prompt, unless oversized), after which everything is same-origin again. -- Pasting a device's printed URL saves it; pasting the same box again refreshes its token rather than duplicating the entry. +- Pasting a device's printed URL saves it; pasting the same device again refreshes its token rather than duplicating the entry. - "Local" remembers the address the dashboard was launched from, so it can find the way back from a remote box. - An indicator names the daemon currently connected: Local on this machine, otherwise the saved device's label or its bare host. diff --git a/packages/the-framework/dashboard/lib/quota-bar.SPEC.md b/packages/the-framework/dashboard/lib/quota-bar.SPEC.md index 9701fcad9..362979486 100644 --- a/packages/the-framework/dashboard/lib/quota-bar.SPEC.md +++ b/packages/the-framework/dashboard/lib/quota-bar.SPEC.md @@ -2,12 +2,12 @@ The arithmetic behind the usage bar — purely about drawing the week; where the ## Flows -- Draws the quota week as real calendar days: segments run local midnight to midnight, so a day's width is how much of it is actually in the week, and a mid-day start's split day is named once, at whichever end holds more of it. +- Draws the quota week as real calendar days: segments run local midnight to midnight, so a day's width is how much of it is actually in the week. A week that starts mid-day splits that day across the bar's two ends; it is labeled once, at whichever end holds more of it. - Day labels are a fixed two-letter notation, not the viewer's locale — two letters of a localized weekday do not distinguish the days in every language. - The bar's colour compares consumption to the boundary with a tolerance band, so on-pace jitter does not flicker it; a fully spent week reads "full", not "over". - The auto-work limit line is computed locally so it moves the instant the slider does; the room between used and limit draws as a dimmer projected stretch. -- The pace gap converts to real time ("2h ahead", "1d behind"), which says more than a share of the week; consumption converts the same way, so the two figures beside each other share a unit rather than asking a reader to convert one into the other. -- Consumption also reads against the allowance elapsed so far rather than against the week, because that is the line which actually parks unattended work — and it declines to read at all while that allowance is still zero. +- The pace gap shows as real time ("2h ahead", "1d behind"), which says more than a share of the week. Consumption converts to the same unit, so the two figures beside each other share a scale instead of asking the reader to convert one into the other. +- A second reading puts consumption against the allowance elapsed so far rather than against the whole week, because that elapsed allowance is the line that actually parks unattended work. While it is still zero — the very start of the week — the reading is omitted rather than shown as infinite. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/lib/ticket-filter.SPEC.md b/packages/the-framework/dashboard/lib/ticket-filter.SPEC.md index f751e978a..ac533812b 100644 --- a/packages/the-framework/dashboard/lib/ticket-filter.SPEC.md +++ b/packages/the-framework/dashboard/lib/ticket-filter.SPEC.md @@ -3,7 +3,7 @@ The tickets page's filtering, sorting, and grouping model: every project's backl ## Flows - The facets: word search, priority/effort/uncertainty, topics, pipeline stage (unplanned / planned / claimed by an agent), project, and locally-written-only. Selections OR within a facet and AND across facets. -- A numeric facet takes named buckets or a fine range — two ways to say the same thing, so picking one clears the other — while "names no value" composes with either: "critical or unprioritized" is a real triage lens. +- A numeric facet takes named buckets or a fine range — two ways to say the same thing, so picking one clears the other. The "no value" option composes with either, so tickets naming no value can be included alongside any selection. - Every facet option shows how many tickets it would reveal under all the other filters, its own facet ignored, so options don't all collapse to zero the moment one is picked. - Sorting puts tickets with no value last in both directions, breaks ties newest-first, and starts each key in its natural direction; sort and grouping reorder but never hide, so they don't count as filters. - A hand-typed URL is input: junk is ignored, and defaults are omitted so the bare page keeps a bare address. diff --git a/packages/the-framework/dashboard/lib/use-agent-handoff.SPEC.md b/packages/the-framework/dashboard/lib/use-agent-handoff.SPEC.md index bee85f9aa..f9fa47ab0 100644 --- a/packages/the-framework/dashboard/lib/use-agent-handoff.SPEC.md +++ b/packages/the-framework/dashboard/lib/use-agent-handoff.SPEC.md @@ -3,7 +3,7 @@ One shared reading of what a finished agent's branch has to hand off — push, o ## Flows - Not read while the agent is still running: a branch still being written to has nothing to hand off yet. -- Re-asked slowly at rest, and quickly while the PR lookup is still out, since that answer decides which buttons to offer; the last answer stays on screen across the cadence switch instead of blanking. +- Re-asked slowly at rest, and quickly while the PR lookup has not answered yet, since that answer decides which buttons to offer. The last answer stays on screen across the cadence switch instead of blanking. - Each action names itself while in flight ("Pushing…" rather than a silently greyed button) and refreshes the answer the moment it lands, so the offer follows the action immediately. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/lib/use-live-events.SPEC.md b/packages/the-framework/dashboard/lib/use-live-events.SPEC.md index d310f7652..5fe5d302d 100644 --- a/packages/the-framework/dashboard/lib/use-live-events.SPEC.md +++ b/packages/the-framework/dashboard/lib/use-live-events.SPEC.md @@ -6,7 +6,7 @@ The live transcript feed: one subscription to the selected agent's own event log - A dead stream is not silent: an errored drop flags the feed as possibly behind reality and retries with backoff, while a deliberate close by the server (watch stream over, unknown session) simply ends it, no alarm. - On a reconnect the pane never shows less than it already showed: the fresh replay is held back and swapped in whole once complete, so recovery is a catch-up rather than a blank-and-refill — and a replay cut short is discarded, never swapped in. - Starting a new agent clears the pane at once, so the finished one's transcript is not shown while the new one spins up. -- An agent's own feed keeps its pre-resume transcript; only the project-wide fallback (used when no agent is addressable) trims itself to the newest one. +- An agent's own feed keeps its pre-resume transcript; only the project-wide fallback (used when no agent is addressable) trims itself to the newest run. ## Before modifying/creating SPEC.md files From 24197823507164dbe5a362d3c255ce3d5eb92f02 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 09:16:38 +0000 Subject: [PATCH 15/16] Style pass: re-voice dashboard components (O-Z, prompt-editor, ui) Component flows open from what the user sees or does; the AI queue, Human Queue, publish ladder, and browser bridge glossed at use; monster bullets split; "you" re-voiced to "the user"; pills/chips vocabulary unified. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- .../dashboard/components/OnboardingChecklist.SPEC.md | 6 +++--- .../dashboard/components/OpenQuestions.SPEC.md | 4 ++-- .../dashboard/components/OptionsMenu.SPEC.md | 2 +- .../dashboard/components/PresetCreatePanel.SPEC.md | 8 ++++++-- .../dashboard/components/PresetsMenu.SPEC.md | 2 +- .../dashboard/components/ProjectErrorBanner.SPEC.md | 2 +- .../dashboard/components/ProjectHome.SPEC.md | 4 ++-- .../dashboard/components/PromptEditor.SPEC.md | 2 +- packages/the-framework/dashboard/components/Quota.SPEC.md | 7 +++++-- .../dashboard/components/RoutineWork.SPEC.md | 2 +- packages/the-framework/dashboard/components/SPEC.md | 2 +- .../dashboard/components/SettingsPage.SPEC.md | 6 +++--- .../dashboard/components/StartAgentForm.SPEC.md | 8 ++++---- .../dashboard/components/SystemPromptDisclosure.SPEC.md | 2 +- .../dashboard/components/TicketDetailPage.SPEC.md | 4 ++-- .../dashboard/components/TicketFilterBar.SPEC.md | 4 ++-- .../dashboard/components/TicketsPage.SPEC.md | 4 ++-- .../dashboard/components/TicketsPanel.SPEC.md | 6 +++--- .../dashboard/components/prompt-editor/SPEC.md | 4 ++-- packages/the-framework/dashboard/components/ui/SPEC.md | 2 +- 20 files changed, 44 insertions(+), 37 deletions(-) diff --git a/packages/the-framework/dashboard/components/OnboardingChecklist.SPEC.md b/packages/the-framework/dashboard/components/OnboardingChecklist.SPEC.md index 308404568..5e1aa5066 100644 --- a/packages/the-framework/dashboard/components/OnboardingChecklist.SPEC.md +++ b/packages/the-framework/dashboard/components/OnboardingChecklist.SPEC.md @@ -2,10 +2,10 @@ The onboarding checklist: what a new install needs, each step shown in the state ## Flows -- Every "done" derives from a real fact — a registered project, a filled AI queue, tickets on disk, a granted notification permission, saved Discord credentials — so a step cannot be ticked by clicking it, and one done outside the dashboard ticks itself. +- Every "done" derives from a real fact — a registered project, a filled AI queue (open items in `TODO_AGENTS.md`), tickets on disk, a granted notification permission, saved Discord credentials — so the user cannot tick a step by clicking it, and a step done outside the dashboard ticks itself. - Only adding a project and filling the AI queue are essential; the rest are marked optional because nothing breaks without them. -- Undone steps carry their own actions: register the current directory or pick one, start an unattended agent that fills `tickets/` from GitHub via the one update preset (landing on it), enable browser notifications, open the Discord setups. -- It renders dismissible on the Overview and permanent on the settings page — which is what dismissing promises you can come back to. +- An undone step offers its fix on the row: register the current directory or pick one, start an unattended agent that fills `tickets/` from GitHub ("Update from GitHub" — the view lands on that agent), enable browser notifications, open the Discord setup. The queue step alone has no button: it ticks once the queue file holds open items. +- The user can dismiss the checklist on the Overview; the settings page copy is permanent — the place the dismiss button itself names for coming back. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/OpenQuestions.SPEC.md b/packages/the-framework/dashboard/components/OpenQuestions.SPEC.md index 4da558b2f..cea112a20 100644 --- a/packages/the-framework/dashboard/components/OpenQuestions.SPEC.md +++ b/packages/the-framework/dashboard/components/OpenQuestions.SPEC.md @@ -2,9 +2,9 @@ The launcher's questions hub: every agent's open question across all projects, a ## Flows -- Each card names the agent asking and offers the way into it; an answer posts against that agent's own record. +- Each card names the agent asking and carries an "Open session" link into it; answering on the card posts against that agent's own record. - An answered card collapses in place to a ✓ line that re-expands to show what was picked — it never vanishes under the cursor — and the memory is per-visit, so a reload starts clean. -- With several questions a stationary jump-nav maps them; with none, the section does not render at all. +- With several questions, a jump list beside the cards holds still while they scroll and jumps to any of them; with none, the section does not render at all. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/OptionsMenu.SPEC.md b/packages/the-framework/dashboard/components/OptionsMenu.SPEC.md index 7ff7f44ca..616879430 100644 --- a/packages/the-framework/dashboard/components/OptionsMenu.SPEC.md +++ b/packages/the-framework/dashboard/components/OptionsMenu.SPEC.md @@ -3,7 +3,7 @@ The options gear: an agent's options as one checkbox dropdown that writes each p ## Flows - "Run on" is one flat list with exactly one checkmark: this machine, a fresh GitHub Actions runner, a hand-off to a Claude web cloud session, then the saved devices — each with a reachability dot and removable in place — and "Add a device". -- Picking a device makes it the target where you are, no navigation; "This machine" while browsing a remote daemon means "go home" instead. +- Picking a device makes it the run target on the spot — no navigation; the local daemon relays agents to it. Picking "This machine" while the dashboard is browsing a remote daemon navigates back home instead. - A disabled option stays visible with the reason it does not apply, and cannot be flipped. - The trigger wears a small dot whenever any option is on. diff --git a/packages/the-framework/dashboard/components/PresetCreatePanel.SPEC.md b/packages/the-framework/dashboard/components/PresetCreatePanel.SPEC.md index bfb7ed92f..d8394718b 100644 --- a/packages/the-framework/dashboard/components/PresetCreatePanel.SPEC.md +++ b/packages/the-framework/dashboard/components/PresetCreatePanel.SPEC.md @@ -1,9 +1,13 @@ -The "New preset" dialog: name and save a prompt as a reusable preset, prefilled with what is currently typed in the composer since the common path is saving what you just wrote. +The "New preset" dialog: name and save a prompt as a reusable preset, prefilled with what is currently typed in the composer. ## Flows - Saving needs both a name and a prompt. -- With a project open you choose where the preset lives: private to you on every project, or committed into the project's repo and shared with the team; with no project open it is always private. +- With a project open the user chooses where the preset lives: private to them on every project, or committed into the project's repo and shared with the team; with no project open it is always private. + +## Rationales + +- The prefill serves the common path: saving what was just written. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/PresetsMenu.SPEC.md b/packages/the-framework/dashboard/components/PresetsMenu.SPEC.md index 9f020e18d..2c76d816c 100644 --- a/packages/the-framework/dashboard/components/PresetsMenu.SPEC.md +++ b/packages/the-framework/dashboard/components/PresetsMenu.SPEC.md @@ -2,7 +2,7 @@ The composer's presets button: the one visible place to load, create, and delete ## Flows -- Three groups: the built-in presets, your own saved presets, and the open project's shared presets; saved presets load their prompt verbatim, and each saved row carries its own delete. +- Three groups: the built-in presets, the user's own saved presets, and the open project's shared presets; saved presets load their prompt verbatim, and each saved row carries its own delete. - Some built-ins always run as an agent of their own, even when loaded from inside one. ## Rationales diff --git a/packages/the-framework/dashboard/components/ProjectErrorBanner.SPEC.md b/packages/the-framework/dashboard/components/ProjectErrorBanner.SPEC.md index 1bd5d0857..f2c2b04ca 100644 --- a/packages/the-framework/dashboard/components/ProjectErrorBanner.SPEC.md +++ b/packages/the-framework/dashboard/components/ProjectErrorBanner.SPEC.md @@ -2,7 +2,7 @@ The banner at the top of a project's page naming what the daemon currently finds ## Flows -- Renders exactly what the project list carries and holds nothing of its own; there is no dismiss. +- The user sees exactly the errors the daemon's project list carries — the banner holds no state of its own, and there is no dismiss. ## Rationales diff --git a/packages/the-framework/dashboard/components/ProjectHome.SPEC.md b/packages/the-framework/dashboard/components/ProjectHome.SPEC.md index b7abc8fe7..d29b82dda 100644 --- a/packages/the-framework/dashboard/components/ProjectHome.SPEC.md +++ b/packages/the-framework/dashboard/components/ProjectHome.SPEC.md @@ -2,8 +2,8 @@ The project home / launcher: the page for starting an agent, plus everything wor ## Flows -- Never consumed by an agent: starting one adds its own view alongside, and this page stays put so you can launch again. -- Anything the daemon finds wrong with the project — a data branch that cannot reach origin — sits as a banner above the start form, until the daemon sees it fixed. +- Starting an agent never consumes this page: the agent gets its own view alongside, and the launcher stays put so the user can start another. +- Anything the daemon finds wrong with the project — say its data branch, the branch the framework keeps its own records on, cannot reach origin — sits as a banner above the start form, until the daemon sees it fixed. - Below the start form: every agent's open questions in one answerable place, then the project's docs — shown in this column rather than in the right rail, which withholds them while this page is up. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/PromptEditor.SPEC.md b/packages/the-framework/dashboard/components/PromptEditor.SPEC.md index 222c06478..c744e8a6b 100644 --- a/packages/the-framework/dashboard/components/PromptEditor.SPEC.md +++ b/packages/the-framework/dashboard/components/PromptEditor.SPEC.md @@ -2,7 +2,7 @@ The rich prompt editor: `/` opens commands, `<` inserts the agent's tag macros, ## Flows -- `/` loads a preset (built-in, yours, or the project's) or inserts an agent action, and can open the create-preset panel; loading over a typed draft is allowed, one undo away, and the caller is told so its note can say so. +- `/` loads a preset (built-in, the user's own, or the project's) or inserts an agent action, and can open the create-preset panel; loading over a typed draft is allowed and one undo away — the form around the editor is told, so its note can say so. - Referencing a project or file also focuses the context on it, and deleting the chip undoes that focus — the prompt and the context set can never silently disagree. - Enter sends, Shift+Enter breaks the line — except while a suggestion menu is open, inside a code block, or mid-IME-composition, where Enter keeps its editing meaning. - Markdown is live; a compact one-line variant serves the navbar quick-launch. diff --git a/packages/the-framework/dashboard/components/Quota.SPEC.md b/packages/the-framework/dashboard/components/Quota.SPEC.md index 60e0f98fd..35793e602 100644 --- a/packages/the-framework/dashboard/components/Quota.SPEC.md +++ b/packages/the-framework/dashboard/components/Quota.SPEC.md @@ -4,12 +4,15 @@ The Usage panel: the account's quota week as one bar, so "am I ahead or behind?" - The bar splits into the used fill and, dimmed, the budget left for autonomous AI; dragging the dimmed segment's own edge sets where unattended work stops, and the legend says whether autonomous AI currently has room (enabled) or none (disabled). - The headline is a duration, not a percentage: how far ahead of or behind the week's pace consumption runs. -- Beside it, two readings of the same spend: how much quota *time* it consumed, and what share of the pro-rata allowance elapsed so far that is — 100% being exactly on pace. The dollar figure says nothing about whether today's rate is sustainable, and a share of the week answers how much is left rather than how fast it is going. The share is absent at the very start of the week, where the allowance so far is zero and every amount is infinitely above it. +- Beside it, two readings of the same spend: how much quota *time* it consumed, and what share of the pro-rata allowance elapsed so far that is — 100% being exactly on pace. The share is absent at the very start of the week, where the allowance so far is zero and every amount is infinitely above it. - Setting the stop more than a full day ahead of pace earns an eager-consumption warning; the other limits (the session, a model's own week) tuck behind "show all limits". ## Rationales -- A week the panel cannot place is a loud error quoting the text it could not read — a quiet fallback would hide a real defect for weeks. Each of the three ways of failing gets its own sentence: an unreadable reset phrasing shows that phrasing; a week line carrying no reset time at all says that, and does not claim a parse failure it did not have, since that readout parsed exactly as printed and simply left the week with only one end of its span; and a readout with no week names the line that is missing and lists the labels that arrived instead. The readout is prose from another program, so those labels are the whole diagnosis — and a message that borrows the wrong case is worse than none, since it denies a week the panel is listing directly below it. +- Both readings are about rate on purpose: a dollar figure says nothing about whether today's rate is sustainable, and a share of the whole week answers how much is left rather than how fast it is going. +- A week the panel cannot place is a loud error quoting the text it could not read — a quiet fallback would hide a real defect for weeks. +- Each of the three ways of failing gets its own sentence. An unreadable reset phrasing shows that phrasing. A week line carrying no reset time at all says exactly that — it does not claim a parse failure, because that readout parsed as printed and simply left the week with only one end of its span. A readout with no week line names the line that is missing and lists the labels that arrived instead. +- The readout is prose from another program, so the quoted labels are the whole diagnosis. A message that borrows the wrong case is worse than none: it would deny a week the panel is listing directly below it. - Numbers that outlive failed refreshes are dated, since an undated bar claims to be current. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/RoutineWork.SPEC.md b/packages/the-framework/dashboard/components/RoutineWork.SPEC.md index fcf6ede5c..f758e22c7 100644 --- a/packages/the-framework/dashboard/components/RoutineWork.SPEC.md +++ b/packages/the-framework/dashboard/components/RoutineWork.SPEC.md @@ -1,4 +1,4 @@ -The Overview's Routine work card: the jobs the scheduled sweep fires, each with a Run now that starts it against a chosen project immediately. +The Overview's Routine work card: the jobs fired by the scheduled sweep — the daemon's recurring pass that starts routine work on its own — each with a Run now that starts it against a chosen project immediately. ## Flows diff --git a/packages/the-framework/dashboard/components/SPEC.md b/packages/the-framework/dashboard/components/SPEC.md index bf5182501..da2a51563 100644 --- a/packages/the-framework/dashboard/components/SPEC.md +++ b/packages/the-framework/dashboard/components/SPEC.md @@ -12,7 +12,7 @@ The dashboard's React component catalog: every page, panel and control the brows - One shared shell frames every route: the left sidebar (brand, New launcher, Overview / Tickets / Projects navigation, recent agents, utility footer) and a right rail of agent-pushed views, surfaced docs and project history. Its pages are the Overview board, the project home/launcher, one agent's view, the cross-project tickets pages (list, per-ticket detail, per-ticket plan), Settings, and not-found. - The agent surface is a transcript with its controls inline: an action bar carrying the branch / PR / handoff and the one menu of agent actions, the event feed rendering its questions as answerable cards and its browser screencast in place, the changes and handoff panels, and one composer that starts, steers, stops and resumes — in a stable frame, so an ending never blanks what you are reading. -- The Overview's widgets each show one slice of what the daemon knows: quota pace, agents working now, the Human Queue, the AI queue, routine work, hot tickets, activity and outcomes, and an onboarding checklist whose steps tick off real facts rather than clicks. +- The Overview's widgets each show one slice of what the daemon knows: quota pace, agents working now, the Human Queue (what currently waits on a person — an agent's question, a PR to review), the AI queue (every project's open `TODO_AGENTS.md` items), routine work, hot tickets, activity and outcomes, and an onboarding checklist whose steps tick off real facts rather than clicks. - The launcher's controls — presets, driver/model and option menus, the Context selector, the system-prompt preview — read and write the same preferences and mappings the agent itself uses, so no surface can disagree with the agent it configures. - Everything renders state the daemon owns (reads poll or stream, writes are daemon calls; components keep only view state), and the house rule is honesty: controls name what will actually happen, no state rides on colour alone, warnings teach before an agent is spent but never block, and empty states name their reason instead of dead-ending. - Two sub-kits supply the raw material: the hand-ported UI primitives and the composer's mention-aware prompt-editor engine. diff --git a/packages/the-framework/dashboard/components/SettingsPage.SPEC.md b/packages/the-framework/dashboard/components/SettingsPage.SPEC.md index 807931452..77001f9e2 100644 --- a/packages/the-framework/dashboard/components/SettingsPage.SPEC.md +++ b/packages/the-framework/dashboard/components/SettingsPage.SPEC.md @@ -1,9 +1,9 @@ -The settings page: every setting in one findable place — appearance, driver and model, where agents execute, agent options, notifications, automation, saved devices and the browser bridge — plus the non-dismissible onboarding checklist. +The settings page: every setting in one findable place — appearance, driver and model, where agents execute, agent options, notifications, automation, saved devices and the browser bridge (the daemon route a browser extension uses to carry a Claude web session's questions back) — plus the non-dismissible onboarding checklist. ## Flows -- Everything here writes the one writable tier — your own settings, which is the default every project starts from. A value that belongs to a repo is committed in that repo's own settings file, and is edited there. -- The agent-options table is the very one the launcher renders, its rules applied identically, and a rule-disabled row stays visible, greyed with its reason — the whole point of the page is being where you come to look. +- Everything here writes the one writable tier — the user's own settings, which is the default every project starts from. A value that belongs to a repo is committed in that repo's own settings file (`the-framework.yml`), and is edited there. +- The agent-options table is the very one the launcher renders, its rules applied identically, and a rule-disabled row stays visible, greyed with its reason — the whole point of the page is being where the user comes to look. - A toggle is a preference; whether it can deliver is a capability: blocked browser notifications and unconfigured Discord channels read as such, with their setup dialogs right beside the toggle. - Typed automation values are clamped to the same bounds the daemon enforces, and an untouched value shows the real default in force rather than a zero nothing is using. - A row with nothing to pick renders nothing at all: an empty dropdown is a control you can open and not use, which reads as broken rather than as "no choices here". diff --git a/packages/the-framework/dashboard/components/StartAgentForm.SPEC.md b/packages/the-framework/dashboard/components/StartAgentForm.SPEC.md index a619d5334..8455cad5f 100644 --- a/packages/the-framework/dashboard/components/StartAgentForm.SPEC.md +++ b/packages/the-framework/dashboard/components/StartAgentForm.SPEC.md @@ -2,10 +2,10 @@ The launcher form that starts an agent in the selected project: the shared compo ## Flows -- A typed prompt starts an attended conversation; a preset starts unattended routine work that ends at settle with its armed handoff firing. -- The options sent and the prompt previewed come from the same mapping the agent uses, so the form cannot disagree with the agent it starts; a picked device relays it there, its secret token riding in memory only. -- Preflight warnings spend words before the agent is spent, never blocking: a driver CLI that cannot start (the GitHub CLI checked only when a PR or merge is armed, nothing probed for Actions or device targets), a repo whose disabled auto-merge makes an armed merge land immediately, and Haiku's known skipping of the finish step, which leaves a publishing run an unmerged draft PR. -- A start answers immediately: an optimistic rail row appears and the view jumps to the agent before its record exists. +- A typed prompt starts an attended conversation. A preset starts unattended routine work: it ends on its own once the agent's work settles, firing the hand-off it was armed with — how far the work publishes, up to push, PR, or merge. +- The options sent and the prompt previewed come from the same mapping the agent uses, so the form cannot disagree with the agent it starts; a picked device relays the start to that machine, the device's secret token riding in memory only — never persisted. +- Preflight warnings spend words before the agent is spent, and never block. They cover: a driver CLI that cannot start — the GitHub CLI checked only when a PR or merge is armed, and nothing probed for Actions or device targets; a repo whose disabled auto-merge makes an armed merge land immediately; and Haiku's known skipping of the finish step, which leaves a publishing run an unmerged draft PR. +- A start answers immediately: an optimistic row for the run appears in the sidebar and the view jumps to the agent before its record exists. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/SystemPromptDisclosure.SPEC.md b/packages/the-framework/dashboard/components/SystemPromptDisclosure.SPEC.md index 47028648f..d02f1bd09 100644 --- a/packages/the-framework/dashboard/components/SystemPromptDisclosure.SPEC.md +++ b/packages/the-framework/dashboard/components/SystemPromptDisclosure.SPEC.md @@ -3,7 +3,7 @@ The "Enhanced System Prompt" disclosure on the launcher: the entire system promp ## Flows - It renders through the same composition the agent itself uses — no second copy of the wrapping logic to drift — and states that nothing else is appended when it starts. -- Its two checkboxes are the two real axes, not new settings: the built-in anti-laziness block, and the framework integration as a whole; transparent mode is the master off-switch that empties the prompt entirely, and the rows always read the way the agent will actually behave. +- Its two checkboxes are the two real axes, not new settings: the built-in anti-laziness block, and the framework integration as a whole. Unticking the integration (transparent mode) is the master off-switch that empties the prompt entirely, and the rows always read the way the agent will actually behave. - The summary's status dot lights only when both axes are fully on, with the state also spelled out for screen readers. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/TicketDetailPage.SPEC.md b/packages/the-framework/dashboard/components/TicketDetailPage.SPEC.md index 17102b5f4..445b56f27 100644 --- a/packages/the-framework/dashboard/components/TicketDetailPage.SPEC.md +++ b/packages/the-framework/dashboard/components/TicketDetailPage.SPEC.md @@ -2,8 +2,8 @@ One ticket's own page: its full markdown and every known fact, plus the Queue ac ## Flows -- Addressed directly by the same ticket filename the list row and the route carry; a ticket deleted or mistyped says "does not exist" rather than rendering blank. -- Queue files the ticket into the AI queue with its priority; once queued it reads so and cannot be pressed twice, while a failed write surfaces and leaves it pressable. +- The user reaches the page by the ticket's filename — the same one the list row and the route carry; a deleted or mistyped ticket says "does not exist" rather than rendering blank. +- Queue files the ticket into the AI queue (`TODO_AGENTS.md`) with its priority; once queued it reads so and cannot be pressed twice, while a failed write surfaces and leaves it pressable. - Nothing times a claim out, so a dead agent's claim stands until a human lifts it here: a claimed ticket names its holder inline and offers Release lock. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/TicketFilterBar.SPEC.md b/packages/the-framework/dashboard/components/TicketFilterBar.SPEC.md index 3847f2263..275d1b718 100644 --- a/packages/the-framework/dashboard/components/TicketFilterBar.SPEC.md +++ b/packages/the-framework/dashboard/components/TicketFilterBar.SPEC.md @@ -1,8 +1,8 @@ -The Tickets page's toolbar: search, faceted filters, sort and grouping — the page's whole viewing state in one row, every control handing an edited copy back to the caller, with option counts read from the unfiltered pool so a pick's yield is visible before it is made. +The Tickets page's toolbar: search, faceted filters, sort and grouping — the page's whole viewing state in one row, the bar owning none of it (every control hands an edited copy back to the page), with option counts read from the unfiltered pool so a pick's yield is visible before it is made. ## Flows -- Numeric facets (priority, effort, uncertainty) offer quick buckets, a fine min–max slider and a "names no value" row; buckets and slider drive one selection — a contiguous bucket pick mirrors onto the slider, and only a selection skipping a middle span dims it (still live, not dead). +- Numeric facets (priority, effort, uncertainty) offer quick buckets, a fine min–max slider and a "names no value" row. Buckets and slider drive one selection: a contiguous bucket pick mirrors onto the slider, and only a selection that skips a middle span dims the slider — dimmed, it still works. - Facets appear only when they have something to say: effort and uncertainty once some plan recorded numbers, Project with two or more projects, "Not linked" while unlinked tickets exist. - Picking a sort key starts at its natural direction, and directions are labelled by meaning per key — "Newest first", "Easiest first" — since "descending" says nothing alone. - "/" focuses the search from anywhere, advertised by a keycap chip that steps aside once the field is in use. diff --git a/packages/the-framework/dashboard/components/TicketsPage.SPEC.md b/packages/the-framework/dashboard/components/TicketsPage.SPEC.md index ad2c47c86..29478d2a7 100644 --- a/packages/the-framework/dashboard/components/TicketsPage.SPEC.md +++ b/packages/the-framework/dashboard/components/TicketsPage.SPEC.md @@ -2,8 +2,8 @@ The cross-project Tickets view: every registered project's backlog on one full-w ## Flows -- The whole viewing state lives in the URL, so a filtered view is a shareable link; changes mirror to the address without adding history steps, and the shown/total tally rides the page title. -- Grouped by project by default, each section its own panel with its GitHub update bar; the flat list is the one view that answers "the single highest-priority ticket anywhere", rows carrying their project and still starting work or plans in it. +- The whole viewing state lives in the URL, so a filtered view is a shareable link; changes mirror to the address without adding history steps, and a shown/total tally sits beside the page heading. +- Grouped by project by default, each section its own panel with its GitHub update bar; the flat list is the one view that answers "the single highest-priority ticket anywhere" — its rows name their project and still start work or plans in it. - Click-to-filter from the rows: a topic badge adds its topic additively, the claim marker narrows to claimed. - A project deselected in its facet disappears entirely — no noise about a choice made on purpose — while filtered-to-nothing states say so and clear from right there. diff --git a/packages/the-framework/dashboard/components/TicketsPanel.SPEC.md b/packages/the-framework/dashboard/components/TicketsPanel.SPEC.md index ac394d82e..c9371cb50 100644 --- a/packages/the-framework/dashboard/components/TicketsPanel.SPEC.md +++ b/packages/the-framework/dashboard/components/TicketsPanel.SPEC.md @@ -1,12 +1,12 @@ -A project's tickets as scannable one-liner rows — start column, title, topics, claim, effort, priority, age, plan column, GitHub link — plus the GitHub update. +A project's tickets as scannable one-liner rows — start column, title, topics, claim, effort, uncertainty, priority, age, plan column, GitHub link — plus the GitHub update. ## Flows - The start column spins up an unattended agent on that one ticket, the ticket named on the agent so its record says what it implements; opening the row goes to the detail page instead — starting is not opening. - The plan column links an existing plan, or starts an agent to write one — attended, because a plan is written for a human to review. - A claimed ticket wears a hammer with its holder named inline: an agent is planning or implementing it. -- Empty or filled, the backlog offers the one GitHub update — its empty branch is the first import — the filled panel adding the stamp of when it last caught up and admitting when there is no record. -- Buttons send their presets' text verbatim — one label, one instruction, wherever offered — and empty-because-filtered says so instead of offering an update for work already done. +- Empty or filled, the backlog offers the same GitHub update — the preset itself treats an empty `tickets/` as the first import — and the filled panel adds the stamp of when it last caught up, admitting when there is no record. +- Buttons send their presets' text verbatim — one label, one instruction, wherever offered — and a list emptied by filters says so instead of offering an update for work already done. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/prompt-editor/SPEC.md b/packages/the-framework/dashboard/components/prompt-editor/SPEC.md index 2f0798ae4..b4f0bdb4c 100644 --- a/packages/the-framework/dashboard/components/prompt-editor/SPEC.md +++ b/packages/the-framework/dashboard/components/prompt-editor/SPEC.md @@ -1,11 +1,11 @@ -The machinery behind the composer's in-editor triggers and token chips: typing a trigger character opens a menu at the caret, and picked or typed tokens become pills that always submit as the exact plain text the agent parses. +The machinery behind the composer's in-editor triggers and token chips: typing a trigger character opens a menu at the caret, and picked or typed tokens become chips — pills in the editor — that always submit as the exact plain text the agent parses. ## Flows - The composer wires four triggers with it: `/` for commands and presets, `<` for the agent's tags, `@` for project references, `#` for file references. - A trigger menu filters as you type, picks with Enter/Tab or a click, and closes on a non-match, so stray trigger characters in prose are never a trap. - Macros, action calls, and references render as chips edited as one unit; a fully hand-typed token becomes a chip too, its casing normalized. -- Loading a preset chip-ifies the plain token strings it contains. +- Loading a preset turns the plain token strings it contains into chips. - The menus work with assistive tech, and while one is open Enter picks from it instead of sending the prompt. ## Rationales diff --git a/packages/the-framework/dashboard/components/ui/SPEC.md b/packages/the-framework/dashboard/components/ui/SPEC.md index 4fe18e1a4..202de0f07 100644 --- a/packages/the-framework/dashboard/components/ui/SPEC.md +++ b/packages/the-framework/dashboard/components/ui/SPEC.md @@ -4,7 +4,7 @@ The dashboard's shared kit of basic interface pieces — buttons, inputs, menus, - Two dialog tiers on purpose: an irreversible action goes through a focus-trapped confirm dialog a stray click cannot dismiss, while ordinary forms and drawers close on Esc or a click outside. - Scrollbars are the app's own — thin, themed, visible only while content overflows — never the OS overlay bar that hides itself. -- The transcript viewport follows the live edge of a streaming conversation, holds your place when you scroll up, and offers a jump-to-latest button. +- The transcript viewport follows the live edge of a streaming conversation, holds the reader's place when they scroll up, and offers a jump-to-latest button. - The sidebar shell adapts by screen: a collapsible rail on desktop whose state survives reloads, a slide-in drawer on mobile. - Popups (menus, popovers, tooltips) share one surface look, and a trigger stays highlighted while its popup is open. From 80916ec745849ef2998c498e567c02eecdab7e88 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 09:17:16 +0000 Subject: [PATCH 16/16] Style pass: re-voice dashboard components (A-N) Flows open from the user's click or glance; coined vocabulary (gate, launcher, worktree, pushed views, the rail) replaced with each spec's plain words or glossed at use; "you" re-voiced to the user; dense sentences split one idea each; two whole-story components gain their User Stories. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011XvEviGLEJZsp1h6iWzgma --- .../dashboard/components/AddProjectPanel.SPEC.md | 8 ++++++-- .../dashboard/components/AgentActionBar.SPEC.md | 6 +++--- .../dashboard/components/AgentActionsMenu.SPEC.md | 8 ++++---- .../dashboard/components/AgentChanges.SPEC.md | 6 +++--- .../dashboard/components/AgentComposer.SPEC.md | 6 ++++-- .../dashboard/components/AgentHandoff.SPEC.md | 8 ++++---- .../dashboard/components/AgentHistory.SPEC.md | 8 ++++---- .../dashboard/components/AgentView.SPEC.md | 10 +++++----- .../dashboard/components/AiQueue.SPEC.md | 8 ++++---- .../dashboard/components/BrowserPanel.SPEC.md | 4 ++++ .../dashboard/components/ChoicePanel.SPEC.md | 4 ++-- .../dashboard/components/CloudAgentNotice.SPEC.md | 4 ++-- .../dashboard/components/Composer.SPEC.md | 11 ++++++----- .../dashboard/components/DashboardPage.SPEC.md | 4 ++-- .../dashboard/components/DevicesSettings.SPEC.md | 2 +- .../dashboard/components/DiscordDialogs.SPEC.md | 2 +- .../dashboard/components/DriverModelMenu.SPEC.md | 3 ++- .../dashboard/components/EventList.SPEC.md | 8 ++++---- .../dashboard/components/FilePreview.SPEC.md | 4 ++-- .../dashboard/components/FileTree.SPEC.md | 6 +++--- .../dashboard/components/GitStatusBar.SPEC.md | 4 ++-- .../dashboard/components/HotTickets.SPEC.md | 4 ++-- .../dashboard/components/InlineBrowser.SPEC.md | 2 +- .../dashboard/components/Markdown.SPEC.md | 2 +- .../dashboard/components/NotificationsMenu.SPEC.md | 4 ++-- 25 files changed, 74 insertions(+), 62 deletions(-) diff --git a/packages/the-framework/dashboard/components/AddProjectPanel.SPEC.md b/packages/the-framework/dashboard/components/AddProjectPanel.SPEC.md index 3d0c6c491..40716f4eb 100644 --- a/packages/the-framework/dashboard/components/AddProjectPanel.SPEC.md +++ b/packages/the-framework/dashboard/components/AddProjectPanel.SPEC.md @@ -1,9 +1,13 @@ The "Add project" modal: registers a repo — or every git repo directly under a folder — with the daemon so it joins the project list. +## User Stories + +- The user adds a repo (or a folder of repos) as a project from the dashboard, and confirms they trust it before anything is installed. + ## Flows -- Adding is a two-step act: submitting the path first shows a trust confirmation, because adding a repo lets the agent read its files and hidden instructions in an untrusted repo can hijack the agent (prompt injection); nothing is installed until trust is confirmed. -- A folder add reports how many repos it registered (and how many already were) instead of finishing silently. +- The user types a path and submits — and nothing is installed yet: a trust confirmation appears first. Adding a repo lets the agent read its files, and hidden instructions in an untrusted repo can hijack the agent (prompt injection), so the install waits until the user confirms trust. +- After a folder add, the user sees how many repos were registered (and how many already were) instead of the modal finishing silently. - It behaves like the dialog it claims to be: Esc closes, Tab stays inside, and focus returns to the control that opened it. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/AgentActionBar.SPEC.md b/packages/the-framework/dashboard/components/AgentActionBar.SPEC.md index 25eb9e29a..8a8d02f40 100644 --- a/packages/the-framework/dashboard/components/AgentActionBar.SPEC.md +++ b/packages/the-framework/dashboard/components/AgentActionBar.SPEC.md @@ -1,10 +1,10 @@ -One agent's action bar: what the agent is on the left — its branch, state, and a summary of what the branch holds, as a disclosure — and what you can do to it on the right. +One agent's action bar: what the agent is on the left — its branch, state, and a summary of what the branch holds, as a disclosure — and what the user can do to it on the right. ## Flows -- The doing collapses into one overflow menu; only the handoff's next step (push / open PR) stays out as a visible button, since it is the one thing that moves the work forward. +- Everything the user can do to the agent collapses into one overflow menu; only the end-of-work hand-off's next step (push / open PR) stays out as a visible button, since it is the one thing that moves the work forward. - The agent's state (exactly one of stopped, ready for merge, failed, building, finished) reads beside the branch facts instead of spending a banner row on one word. -- One bar whether the agent is live or finished, and one row always: the label gives up width before the controls ever wrap. +- The user sees one bar whether the agent is live or finished, and always one row: the label gives up width before the controls ever wrap. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/AgentActionsMenu.SPEC.md b/packages/the-framework/dashboard/components/AgentActionsMenu.SPEC.md index a836685fb..e96708f49 100644 --- a/packages/the-framework/dashboard/components/AgentActionsMenu.SPEC.md +++ b/packages/the-framework/dashboard/components/AgentActionsMenu.SPEC.md @@ -1,11 +1,11 @@ -The agent's one overflow menu: everything you can do to an agent — open it on GitHub, in the file manager, an editor or the driver's own app, copy the terminal resume command, stop it, arm a merge, remove its worktree, delete it. +The agent's one overflow menu: everything the user can do to an agent — open it on GitHub, in the file manager, an editor or the driver's own app, copy the terminal resume command, stop it, arm a merge, remove its worktree (the agent's own checkout), delete it. ## Flows - Items name what they will actually do: once a clean agent's worktree is gone, the folder item admits it opens the project folder, not an agent folder it can no longer reach. -- The driver's session id is shown as the only handle on the conversation outside the dashboard; clicking it copies the command that reopens it in a terminal, recreating the vanished directory first, since that is how the agent's CLI finds the session. -- Stop and "Merge when finished" exist only while the agent is live; the merge is a pre-commitment honoured at the agent's natural end, so it reads "armed" rather than staying pressable. -- Delete confirms first — the history goes for good; the branch and PR stay in git. +- The driver's session id is shown because it is the only handle on the conversation outside the dashboard. Clicking it copies the command that reopens the conversation in a terminal; the command recreates the vanished directory first, because the driver's CLI finds a session by the directory it ran in. +- Stop and "Merge when finished" exist only while the agent is live. The merge is a pre-commitment honoured at the agent's natural end, so once pressed it reads "armed" rather than staying pressable. +- Deleting asks the user to confirm first — the history goes for good; the branch and PR stay in git. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/AgentChanges.SPEC.md b/packages/the-framework/dashboard/components/AgentChanges.SPEC.md index bbd21db39..b4971f6f8 100644 --- a/packages/the-framework/dashboard/components/AgentChanges.SPEC.md +++ b/packages/the-framework/dashboard/components/AgentChanges.SPEC.md @@ -2,9 +2,9 @@ The live agent's changed-files panel: which files it has touched so far, each ro ## Flows -- Derived from the agent's own working copy, not from its tool calls — the outcome rather than the intent, and it works the same for every driver. -- Diffs load only when a row is opened; the running totals (files, lines added/removed) are reported up so the action bar can show them while the list is collapsed. -- Live agents only: a finished agent is answered by the handoff, which survives the working copy's removal — and this panel must never be shown after that removal, or it would present your own uncommitted files as the agent's work. +- The list is derived from the agent's own working copy, not from its tool calls — it reports the outcome rather than the intent, and works the same for every driver. +- A row's diff loads only when the user opens it. The running totals (files, lines added/removed) are reported up, so the action bar shows them while the list is collapsed. +- Live agents only: a finished agent is answered by the end-of-work handoff, which reads from the branch and so survives the working copy's removal. This panel must never be shown after that removal — it would present the user's own uncommitted files as the agent's work. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/AgentComposer.SPEC.md b/packages/the-framework/dashboard/components/AgentComposer.SPEC.md index 0e83e2a86..82616fd11 100644 --- a/packages/the-framework/dashboard/components/AgentComposer.SPEC.md +++ b/packages/the-framework/dashboard/components/AgentComposer.SPEC.md @@ -2,13 +2,15 @@ One composer for an agent, live or finished: the box stays put across its whole ## Flows -- Live, a send queues a message the agent reads between turns — and says so, since a queued message is otherwise invisible. Ended with a session to pick up, a send continues that same conversation on the same branch and row, on the agent's own driver (never the global preference). Ended without one, a send starts a fresh agent — the placeholder itself says so. +- While the agent runs, a send queues the message for the agent to read between turns — and says so, since a queued message is otherwise invisible. +- Once the agent has ended with a session id to resume, a send continues that same conversation on the same branch and row, on the agent's own driver (never the global preference). +- Once it has ended without one, a send starts a fresh agent — the placeholder itself says so. - The empty box's submit slot doubles as the agent's control: Stop while live, Resume once stopped — both latched so a landed press cannot re-fire or flicker while the state change is still in flight. - A preset marked new-session always opens its own agent, whatever state this one is in. ## Rationales -- One composer rather than one per state: swapping boxes at the ending would remount the editor under the user and take a half-typed message with it, and a box that exists only when resuming is possible would leave an un-resumable agent a dead end. +- One composer rather than one per state: swapping boxes at the ending would remount the editor under the user and take a half-typed message with it. And a box that exists only when resuming is possible would leave an un-resumable agent a dead end. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/AgentHandoff.SPEC.md b/packages/the-framework/dashboard/components/AgentHandoff.SPEC.md index 182b4b442..61cd90111 100644 --- a/packages/the-framework/dashboard/components/AgentHandoff.SPEC.md +++ b/packages/the-framework/dashboard/components/AgentHandoff.SPEC.md @@ -2,10 +2,10 @@ The end-of-work handoff riding the agent's action bar: what this agent left behi ## Flows -- The one-line verdict beside the branch: "branch gone", "no changes", or commits · files · diffstat, plus whether the work is already pushed or merged. -- While the agent works, one checkbox arms the ending — ticked by default, so finished work stops stranding on local branches nobody was told about. Its label names exactly what this agent will do (open a PR, open and merge, or push only); unticked, nothing is handed off. Re-ticking lands on the zero-config rung rather than restoring a merge the box never mentioned, so the label and the outcome always agree. -- Once it settles, deliberate buttons take over: Open PR, or Merge for an open unmerged PR — never while the PR lookup is still out, since acting on "not known yet" is how second PRs get opened. -- With nothing to press, the reason is said instead: branch gone, nothing committed (uncommitted files named), or no remote. +- The user reads a one-line verdict beside the branch: "branch gone", "no changes", or commits · files · diffstat, plus whether the work is already pushed or merged. +- While the agent works, one checkbox arms the ending — ticked by default, so finished work stops stranding on local branches nobody was told about. Its label names exactly what this agent will do (open a PR, open and merge, or push only); unticked, nothing is handed off. Re-ticking arms the default action — open a PR — never a merge the box never mentioned, so the label and the outcome always agree. +- Once the agent stops working — even if its process stays alive awaiting a next message — deliberate buttons take over: Open PR, or Merge for an open unmerged PR. Neither is offered while the PR lookup is still out, since acting on "not known yet" is how second PRs get opened. +- With nothing to press, the user reads the reason instead: branch gone, nothing committed (uncommitted files named), or no remote. - The bar's disclosure expands into commits, changed and uncommitted files, capped with the remainder counted. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/AgentHistory.SPEC.md b/packages/the-framework/dashboard/components/AgentHistory.SPEC.md index 25b63e329..bd4622849 100644 --- a/packages/the-framework/dashboard/components/AgentHistory.SPEC.md +++ b/packages/the-framework/dashboard/components/AgentHistory.SPEC.md @@ -4,10 +4,10 @@ The app's left sidebar, present on every route: brand, global navigation (New, O - "New" adapts to what exists: no projects prompts to add one, one project starts there, several open a picker; inside a project it starts another agent there. - The rail lists the selected project's agents — or, on the Overview, every project's pooled newest-first, each row naming its project and jumping into it. -- A row says what its agent is really doing: pulsing while it works, a still "waiting" when parked on you, "publishing…" while an ended one still pushes or opens its PR, "in cloud" for a web agent continuing elsewhere — plus device/cloud glyphs and the driver's logo. -- Start seeds an optimistic "starting…" row that retires when the real agent lands, whatever state it lands in, with a deadline so a start that produced nothing does not pretend forever. -- The Overview item carries the Human Queue count. -- A project the daemon has found something wrong with — a data branch that cannot reach origin — gets a red dot in the Projects list, naming the error on hover. +- A row says what its agent is really doing: pulsing while it works, a still "waiting" when parked on the user, "publishing…" while an ended one still pushes or opens its PR, "in cloud" for a web agent continuing elsewhere — plus device/cloud glyphs and the driver's logo. +- Clicking Start immediately seeds a provisional "starting…" row; it retires when the real agent lands, whatever state it lands in, and a deadline stops a start that produced nothing from pretending forever. +- The Overview item carries the Human Queue count — how many items across every project await a person. +- A project the daemon has found something wrong with — its data branch (`tf-data`, where the framework archives its records) cannot reach origin — gets a red dot in the Projects list, naming the error on hover. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/AgentView.SPEC.md b/packages/the-framework/dashboard/components/AgentView.SPEC.md index 91d9e11f1..5122d55e8 100644 --- a/packages/the-framework/dashboard/components/AgentView.SPEC.md +++ b/packages/the-framework/dashboard/components/AgentView.SPEC.md @@ -2,11 +2,11 @@ One agent's view, live or finished, in a stable frame — bar, details, changes/ ## Flows -- The transcript is one log with two sources — the live channel while running, the archive once ended — swapped behind the events on screen, so an ending never blanks what you are reading. -- An empty archive never replaces shown events, a stale one never hides a resumed leg, a foreign journal never beats the agent's own record; the archive is re-read once the feed outgrows it, which is how the epilogue's PR line arrives without a refresh. -- "Done" means the agent settled, not that the process died: a parked agent stays alive for your next message, so the handoff keys off settling. -- While working, the bar summarizes the worktree's changes; after settle it swaps, once loaded, to what the branch holds. -- Agents executing elsewhere say so: the Actions burst-at-the-end replay, the web agent's cloud session, the device agent keeping only its preview local. +- The transcript is one log with two sources — the live channel while running, the archive once ended — swapped behind the events on screen, so an ending never blanks what the user is reading. +- Three guards keep that swap honest: an empty archive never replaces events already shown, a stale archive never hides a resumed agent's newest events, and a channel carrying some other run's log — the server's fallback once this agent's checkout is gone — never beats the agent's own archive. The archive is re-read once the live feed outgrows it, which is how the PR line written after the agent ends arrives without a refresh. +- "Done" means the agent settled — finished its work — not that its process died: a parked agent stays alive for the user's next message, so the handoff keys off settling. +- While the agent works, the bar summarizes its checkout's changes; once it settles, the summary swaps — only after the branch read has loaded, never blanking — to what the branch holds. +- An agent executing elsewhere says so in place: a GitHub Actions run's log arrives as one burst at the end (the wait is named, with a link to the live run), a web run points at its Claude cloud session, and a run on a connected device flags that only its browser preview stays on the device. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/AiQueue.SPEC.md b/packages/the-framework/dashboard/components/AiQueue.SPEC.md index 9f6ea4f31..901fa4347 100644 --- a/packages/the-framework/dashboard/components/AiQueue.SPEC.md +++ b/packages/the-framework/dashboard/components/AiQueue.SPEC.md @@ -1,11 +1,11 @@ -The Overview's AI Queue card: every project's open queue entries — the work the framework picks up on its own — shown in full, each readable and startable. +The Overview's AI Queue card: every project's open entries in its queue file (`TODO_AGENTS.md`) — the work the framework picks up on its own — shown in full, each readable and startable. ## Flows -- An entry's title opens what it names: a queued ticket opens its own ticket page in-app, an external link opens in a new tab, and plain text pretends to be nothing. -- The play button starts one unattended agent on that entry alone — the same work the automatic drain sweep would get to, on your click — then jumps to the agent it started. +- An entry's title opens what it names: a queued ticket opens its own ticket page in-app, an external link opens in a new tab, and a plain-text entry is not clickable at all. +- The play button starts one unattended agent on that entry alone — the same work the framework's periodic queue drain would get to, now on the user's click — then jumps to the agent it started. - The agent is prompted with the raw queue line rather than the pretty title, so it can find exactly that entry and check it off. -- A refused start stays put and shows the reason; the plan is never collapsed behind a "+N more". +- A refused start stays put and shows the reason; the queue is never collapsed behind a "+N more". ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/BrowserPanel.SPEC.md b/packages/the-framework/dashboard/components/BrowserPanel.SPEC.md index 381e34c1f..3e24485c4 100644 --- a/packages/the-framework/dashboard/components/BrowserPanel.SPEC.md +++ b/packages/the-framework/dashboard/components/BrowserPanel.SPEC.md @@ -1,5 +1,9 @@ The live view and remote control of the agent's own browser — in the right rail or inline in the transcript — which is what lets a human get the agent past a login wall. +## User Stories + +- The user watches the agent's browser live and drives the real page — clicking, scrolling, typing — to get the agent past a login wall, captcha, or 2FA. + ## Flows - The browser streams in as live video, and clicks, scrolls, and typed keys go back to the real page, with clicks rescaled so they land where they look like they land. diff --git a/packages/the-framework/dashboard/components/ChoicePanel.SPEC.md b/packages/the-framework/dashboard/components/ChoicePanel.SPEC.md index 7cf9f7f96..2976a0ca4 100644 --- a/packages/the-framework/dashboard/components/ChoicePanel.SPEC.md +++ b/packages/the-framework/dashboard/components/ChoicePanel.SPEC.md @@ -3,9 +3,9 @@ ## Flows - One shape: a list of the agent's options with the recommended one highlighted, or — when the question takes several answers — a checklist whose Accept button says exactly what it will send (down to "Accept none"). An Approve/Decline approval is two options like any other. -- It always asks. A gate only reaches a panel because somebody is watching; an agent nobody is watching resolves its gates to the recommended option without one, so the audience is the signal rather than a setting. +- It always asks. A question only reaches a panel because somebody is watching; an agent nobody is watching resolves its questions to the recommended option without ever showing one, so the audience is the signal rather than a setting. - A sent answer parks the panel — buttons off, "waiting for the agent to pick it up" — until the agent confirms and the question goes away. -- The first open gate also answers to Ctrl+Enter, so the shortcut is unambiguous with several gates open. +- The first open question also answers to Ctrl+Enter, so the shortcut is unambiguous with several questions open. ## Rationales diff --git a/packages/the-framework/dashboard/components/CloudAgentNotice.SPEC.md b/packages/the-framework/dashboard/components/CloudAgentNotice.SPEC.md index 2365791c9..c73b20d89 100644 --- a/packages/the-framework/dashboard/components/CloudAgentNotice.SPEC.md +++ b/packages/the-framework/dashboard/components/CloudAgentNotice.SPEC.md @@ -1,9 +1,9 @@ -What the agent view shows when the work was handed to a Claude web cloud session: where it went and how to reach it, plus — through the browser bridge — the question it is parked on and a mirror of its conversation. +What the agent view shows when the work was handed to a Claude web cloud session: where it went and how to reach it, plus — through the browser-extension bridge that watches the user's own Claude tab — the question it is parked on and a mirror of its conversation. ## Flows - Nothing streams back from a cloud session, so instead of a feed that looks stalled the notice says the honest thing — the session asks its questions and opens its pull request over there — with a link out and the command that continues it on this machine. -- A question the bridge reports is answerable here as pick-then-confirm: the send has the extension type into the user's own Claude tab, so a queued answer can still be withdrawn, and a failed delivery says so and re-offers the question. +- A question the bridge reports is answerable here as pick-then-confirm: the send has the extension type the answer into the user's own Claude tab. Until the extension collects it, the queued answer can still be withdrawn; a failed delivery says so and re-offers the question. - The mirror is one clearly labelled box of what the Claude tab shows — a best-effort scrape kept visibly apart from the agent's own durable log, with the site's UI chrome scrubbed out. - Both pieces render nothing for any other target, so agent views mount them unconditionally. diff --git a/packages/the-framework/dashboard/components/Composer.SPEC.md b/packages/the-framework/dashboard/components/Composer.SPEC.md index dc44c436c..d3d91a810 100644 --- a/packages/the-framework/dashboard/components/Composer.SPEC.md +++ b/packages/the-framework/dashboard/components/Composer.SPEC.md @@ -1,11 +1,12 @@ -The shared composer: the prompt editor plus its control row — presets, context, driver and model, options, send — used by the launcher to start agents and by an agent's chat to message it. +The shared composer: the prompt editor plus its control row — presets, context, driver and model, options, send — used by the launcher (the project's start-an-agent screen) and by an agent's chat to message it. ## Flows -- A loaded preset prefills the editor and runs verbatim as its own kind; emptying the box falls back to a normal build, and a preset can insist on opening a new agent. -- Agent, model, options, and target are shared preferences: every composer, including the compact navbar row, shows and writes the same state instead of silently using stored values. -- On an agent, controls that can no longer change anything disappear — the driver is bound at start, and the options gear hides while it is live, returning after the end offering only what a Resume actually re-arms. -- A draft carried from another device or a navigating click lands in the launcher's editor, taken once; Start is blocked with a reason while the chosen device is offline, and fast double-submits fire once. +- A loaded preset prefills the editor and runs verbatim as its own run kind; emptying the box falls back to a normal build run, and a preset can insist on opening a new agent. +- Driver, model, options, and target are shared preferences: every composer, including the compact navbar row, shows and writes the same state instead of silently using stored values. +- On an agent, controls that can no longer change anything disappear: the driver select goes (the driver is bound at start), and the options gear hides while the agent is live, returning after the end offering only what a Resume actually re-arms. +- A draft carried from another device or from a navigating click lands in the launcher's editor, taken once. +- Start is blocked with a reason while the chosen target device is offline, and fast double-submits fire once. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/DashboardPage.SPEC.md b/packages/the-framework/dashboard/components/DashboardPage.SPEC.md index 100fe9bea..2adce774c 100644 --- a/packages/the-framework/dashboard/components/DashboardPage.SPEC.md +++ b/packages/the-framework/dashboard/components/DashboardPage.SPEC.md @@ -1,8 +1,8 @@ -The Overview landing page, shown when no project is picked: usage first, then what needs a human beside who is working now and what the AI takes up next, the routine jobs, and the hot tickets across every project — polled so it stays live. +The Overview landing page, shown when no project is picked: usage first, then what needs a human, who is working now, what the AI takes up next, the routine jobs, and the hot tickets across every project — polled so it stays live. ## Flows -- Every card is a projection of the same project files; selecting a row jumps into its project, straight into an agent, or onto a ticket's own page. +- Every card is a projection of the same per-project files the daemon reads. Selecting a row jumps into its project, straight into an agent, or onto a ticket's own page. - The Human Queue collects the cross-project things only a person can clear: open pull requests to review (merge to confirm, close to reject — each links out), agents parked mid-flight on a question (the row opens the agent to answer), and finished work that was never pushed. Notifications fire off the same set. - A new install sees the onboarding checklist on top until dismissed; dismissing hides it only here, the settings page keeps it. diff --git a/packages/the-framework/dashboard/components/DevicesSettings.SPEC.md b/packages/the-framework/dashboard/components/DevicesSettings.SPEC.md index 7f68c1e29..f8e737842 100644 --- a/packages/the-framework/dashboard/components/DevicesSettings.SPEC.md +++ b/packages/the-framework/dashboard/components/DevicesSettings.SPEC.md @@ -2,7 +2,7 @@ The settings section for the saved devices — other machines running The Framew ## Flows -- Unlike everything else in settings these are not preferences: a device carries its own access token, so the roster lives in this browser only and does not follow you to the next one — and the section says so. +- Unlike everything else in settings these are not preferences: a device carries its own access token, so the roster lives in this browser only and does not follow the user to the next one — and the section says so. - Removing the device currently chosen as the target also clears that choice, so the next agent cannot point at a machine no longer on the list. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/DiscordDialogs.SPEC.md b/packages/the-framework/dashboard/components/DiscordDialogs.SPEC.md index c17f5dee9..dad91bb69 100644 --- a/packages/the-framework/dashboard/components/DiscordDialogs.SPEC.md +++ b/packages/the-framework/dashboard/components/DiscordDialogs.SPEC.md @@ -3,7 +3,7 @@ The two Discord setup dialogs — the bot and notifications — that explain the ## Flows - A credential is write-only: it goes to the daemon and never comes back, so a stored one reads "saved" with Replace and Remove instead of a field holding a secret. -- A credential set in the daemon's environment wins over a stored one, so that case is reported as fixed rather than offering an edit the daemon would ignore; a host that stores no credentials says so instead of offering a field. +- A credential set in the daemon's environment wins over a stored one, so that case is reported as set-by-environment and not editable here, rather than offering an edit the daemon would ignore. A host that stores no credentials says so instead of offering a field. - Obviously-wrong input is refused before it is sent, and anything typed is wiped when the dialog closes so no secret lingers in a field. - The enable toggle is independent of the credential: it can be turned on first and starts working once the credential is set. - Each dialog's one-line description is shared with the onboarding checklist row that opens it, so the two never tell different stories. diff --git a/packages/the-framework/dashboard/components/DriverModelMenu.SPEC.md b/packages/the-framework/dashboard/components/DriverModelMenu.SPEC.md index b79d429f5..e20e34ee5 100644 --- a/packages/the-framework/dashboard/components/DriverModelMenu.SPEC.md +++ b/packages/the-framework/dashboard/components/DriverModelMenu.SPEC.md @@ -1,7 +1,8 @@ -One menu picking the driver and its model together — drivers at the top level, each opening a submenu of only its own models, so a pick sets both at once and an incompatible pair can never be chosen; the trigger wears the current driver's logo and, when one is pinned, its model. +One menu picking the driver and its model together: drivers at the top level, each opening a submenu of only its own models, so a pick sets both at once and an incompatible pair can never be chosen. ## Flows +- The trigger wears the current driver's logo and, when one is pinned, its model. - Every listed model is a real model id; there is no "Default" entry. - Not choosing is still a state, and the trigger says so rather than naming the first model in the list. - The trigger carries its own accessible name, because with no model pinned its rendered content is a logo and a chevron. diff --git a/packages/the-framework/dashboard/components/EventList.SPEC.md b/packages/the-framework/dashboard/components/EventList.SPEC.md index 23dc51403..1f86f5c3b 100644 --- a/packages/the-framework/dashboard/components/EventList.SPEC.md +++ b/packages/the-framework/dashboard/components/EventList.SPEC.md @@ -2,11 +2,11 @@ The agent's transcript, shared by the live stream and the replay of a finished o ## Flows -- Your prompts read YOU (blue) and the agent's replies AGENT, both as Markdown; a long message collapses to its first line and expands in place, and the system prompt hides behind a character count. -- The agent's first prompt is hoisted to the very top, so the log opens with what you asked rather than the machinery that preceded it; later turns stay where they happened. -- Colour carries meaning: failures red, your own turn blue, decision badges amber, milestones green, pushed surfaces primary — and a stopped agent is not an error, since stopping was asked for. +- The user's prompts read YOU (blue) and the agent's replies AGENT, both as Markdown. A long message collapses to its first line and expands in place, and the system prompt hides behind a character count. +- The agent's first prompt is hoisted to the very top, so the log opens with what the user asked rather than the machinery that preceded it; later turns stay where they happened. +- Colour carries meaning: failures red, the user's own turn blue, decision badges amber, milestones green, the surfaces the agent pushes (views, previews, browser rows) primary — and a stopped agent is not an error, since stopping was asked for. - When the transcript knows its agent, a decision row IS the interaction: an open question is answerable in place, an answered one collapses to a ✓ card, and one whose agent ended stays plain text — its audience is gone. -- The latest browser row hosts the live inline preview — one screencast, with re-said pages replacing their earlier row rather than stacking — and the live log follows the newest row but yields the moment the reader scrolls up. +- The latest browser row hosts the live inline preview — there is one screencast, and a page announced again replaces its earlier row rather than stacking. The live log follows the newest row but yields the moment the reader scrolls up. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/FilePreview.SPEC.md b/packages/the-framework/dashboard/components/FilePreview.SPEC.md index ce943baf9..aa0ec08c2 100644 --- a/packages/the-framework/dashboard/components/FilePreview.SPEC.md +++ b/packages/the-framework/dashboard/components/FilePreview.SPEC.md @@ -1,10 +1,10 @@ -Hovering a file in the tree shows what is in it — a changed file's diff, an unchanged file's contents — read from the selected agent's worktree. +Hovering a file in the tree shows what is in it — a changed file's diff, an unchanged file's contents — read from the selected agent's own checkout. ## Flows - Nothing is read until a card actually opens, so the tree's many hover targets cost nothing for files never pointed at. - An open card re-reads every few seconds, keeping up with an agent that is still editing instead of freezing at hover time. -- The status the tree already holds decides diff-versus-contents, so the server is not asked the same question twice; binary, empty, cut-off, and unreadable files are named as such rather than shown raw or left on a spinner. +- The status the tree already holds decides diff-versus-contents, so the server is not asked the same question twice. Binary, empty, cut-off, and unreadable files are named as such rather than shown raw or left on a spinner. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/FileTree.SPEC.md b/packages/the-framework/dashboard/components/FileTree.SPEC.md index 88dfd82fa..b25b4a039 100644 --- a/packages/the-framework/dashboard/components/FileTree.SPEC.md +++ b/packages/the-framework/dashboard/components/FileTree.SPEC.md @@ -1,11 +1,11 @@ -The project panel's file tree — a context picker, not an editor: clicking a file toggles it in the Context, the same set the other context pickers feed. +The project panel's file tree — a context picker, not an editor: clicking a file toggles it in the Context (the set of files handed to the agent), the same set the other context pickers feed. ## Flows -- Per-file git-status marks, read from the selected agent's worktree and refreshed as it edits, roll up to folders so dirty work is spottable even while a folder is closed. A file says which change it is; a folder only says that something under it changed. +- Per-file git-status marks, read from the selected agent's own checkout and refreshed as it edits, roll up to folders so dirty work is spottable even while a folder is closed. A file says which change it is; a folder only says that something under it changed. - A filter box narrows to matching files, and zero matches say so instead of rendering an empty pane that reads as broken. - Every file previews on hover — its diff when changed, its contents when not — with the tree's own status deciding which. -- Localhost-only: with no checkout to list (the relay), the tree renders nothing. +- Localhost-only: the relay — watching an agent that executes on another machine — has no checkout to list here, so the tree renders nothing. ## Rationales diff --git a/packages/the-framework/dashboard/components/GitStatusBar.SPEC.md b/packages/the-framework/dashboard/components/GitStatusBar.SPEC.md index e493e9714..622529aae 100644 --- a/packages/the-framework/dashboard/components/GitStatusBar.SPEC.md +++ b/packages/the-framework/dashboard/components/GitStatusBar.SPEC.md @@ -2,8 +2,8 @@ The checkout-in-play status line — active branch, a clean/dirty dot, the linke ## Flows -- On an agent it reports that agent's own worktree, adding what only a worktree has: its size on disk, and honesty that uncommitted changes there are the agent's, not yours. -- Refreshed on a slow cadence, but sped up while a PR lookup is still settling, so that answer appears in seconds rather than after a full cycle; nothing renders when there is no checkout to report. +- On an agent it reports that agent's own checkout, adding what only an agent's checkout carries: its size on disk, and honesty that uncommitted changes there are the agent's, not the user's. +- Refreshed on a slow cadence, but sped up while a PR lookup is still settling, so that answer appears in seconds rather than after a full cycle. Nothing renders when there is no checkout to report. - The agent's name leads and truncates last — it is the stable identity, where the branch gets renamed by the agent — and other facts drop out whole as the bar narrows rather than squeezing. - It can double as the disclosure for the branch detail below it, so an agent's branch is spoken about in exactly one place. diff --git a/packages/the-framework/dashboard/components/HotTickets.SPEC.md b/packages/the-framework/dashboard/components/HotTickets.SPEC.md index 14d61df3a..82c032c59 100644 --- a/packages/the-framework/dashboard/components/HotTickets.SPEC.md +++ b/packages/the-framework/dashboard/components/HotTickets.SPEC.md @@ -1,10 +1,10 @@ -The Overview's cross-project shortlist of the tickets that matter right now: what is being worked on, what sits in the AI Queue, and what is flagged high priority. +The Overview's cross-project shortlist of the tickets that matter right now: what is being worked on, what sits in the AI Queue (the work queued for the framework to pick up on its own), and what is flagged high priority. ## Flows - Three lanes, every qualifying ticket listed in full — never "+N more" — and the empty state names the lanes rather than claiming the backlog is empty. - A ticket an agent is implementing right now says so — live work outranks the "planned" mark older work left behind — and its row opens that agent. -- A ticket with no agent opens its project's launcher prefilled with a plain, editable "work on this ticket" draft naming the ticket file, so the click is never a dead end and there is no hidden second version of the ask to drift. +- A ticket with no agent opens its project's launcher — the start-an-agent composer — prefilled with a plain, editable "work on this ticket" draft naming the ticket file. The click is never a dead end, and the draft is the ask itself — no hidden second version to drift. ## Before modifying/creating SPEC.md files diff --git a/packages/the-framework/dashboard/components/InlineBrowser.SPEC.md b/packages/the-framework/dashboard/components/InlineBrowser.SPEC.md index 3a96ae662..ac4234cd8 100644 --- a/packages/the-framework/dashboard/components/InlineBrowser.SPEC.md +++ b/packages/the-framework/dashboard/components/InlineBrowser.SPEC.md @@ -1,4 +1,4 @@ -The browser preview inline in the transcript — the same proxied screencast the rail's Browser tab shows, so the two surfaces can never disagree about what the browser is doing. +The browser preview inline in the transcript — the same proxied screencast the right rail's Browser tab shows, so the two surfaces can never disagree about what the browser is doing. ## Flows diff --git a/packages/the-framework/dashboard/components/Markdown.SPEC.md b/packages/the-framework/dashboard/components/Markdown.SPEC.md index 2f58459e2..13a54fad2 100644 --- a/packages/the-framework/dashboard/components/Markdown.SPEC.md +++ b/packages/the-framework/dashboard/components/Markdown.SPEC.md @@ -1,4 +1,4 @@ -A tiny, dependency-free Markdown renderer for agent-written content: the surfaced PLAN/TODO docs, pushed views, and conversation messages. +A tiny, dependency-free Markdown renderer for agent-written content: the surfaced PLAN/TODO docs, the views the agent pushes to the dashboard, and conversation messages. ## Flows diff --git a/packages/the-framework/dashboard/components/NotificationsMenu.SPEC.md b/packages/the-framework/dashboard/components/NotificationsMenu.SPEC.md index 03230cf5a..c75a6987e 100644 --- a/packages/the-framework/dashboard/components/NotificationsMenu.SPEC.md +++ b/packages/the-framework/dashboard/components/NotificationsMenu.SPEC.md @@ -2,8 +2,8 @@ The shell header's single notifications bell, making the model legible: where no ## Flows -- The bell lights only when a method would actually deliver: browser needs its permission granted, Discord needs a configured credential on top of the toggle — a switch that delivers nothing must not read as "you will be paged". -- "Human Queue" (an agent awaiting you, a PR to review) is the default-on baseline but a real toggle; "New activity" (an agent started or finished) is opt-in on top. +- The bell lights only when a method would actually deliver: browser needs its permission granted, Discord needs a configured credential on top of the toggle — a switch that delivers nothing must not read as a promise to page the user. +- "Human Queue" (an agent awaiting the user, or a PR to review) is the default-on baseline but a real toggle; "New activity" (an agent started or finished) is opt-in on top. - Turning browser delivery on asks the browser for permission on that same click, and a permission blocked in browser settings disables the toggle with the reason. ## Before modifying/creating SPEC.md files