Repository navigation
Generic collections (factory_job refs) and client tools (integration branch; draft, do not merge) - #2306
Draft
AbirAbbas wants to merge 53 commits into
Draft
Generic collections (factory_job refs) and client tools (integration branch; draft, do not merge)#2306AbirAbbas wants to merge 53 commits into
AbirAbbas wants to merge 53 commits into
Conversation
The Workstreams feature lands on workstreams/main in work-package pull requests before it goes to app/v3. Listing the line here gives each of those pull requests the light gate and its touched packages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
) The Workstreams plan, WP-0: Go types for the 25 workstream, page, record and goahead methods and the workstream.changed event, registered through typed() and delegating to an optional Workstreamer; appBackend answers every one unknown_method "workstreams: not implemented yet" and does not announce the workstreams feature until the store lands (WP-1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…2277) * app: workstream pages render from editor JSON to markdown The Workstreams plan, WP-6c: internal/workstream/pagedoc.Markdown renders a page's editor JSON by the page document format rules, byte for byte the same as the desktop app's renderer, so a job that reads a page sees the markdown a person sees when they export it. Pure: no I/O and no store; invalid JSON is an error, an absent document is empty, unknown nodes keep their text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tests: page markdown matches the desktop app's fixtures and the contract example Every assertion of the desktop app's markdown tests as a Go table row with the identical expected string, the contract's worked example as testdata, invalid and absent documents, and edge cases that pin JavaScript's line terminators, number formatting and coercions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: change entry for the workstream page renderer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ved) (#2270) * collections: schema 3 with workstream tables and the factory_job ref kind The collections database moves to schema 3. The first write rebuilds a schema 1 or 2 file by copying its tables (SQLite cannot change a CHECK in place), keeping every collection and membership under the same sequence numbers and times, and adds the workstream tables: rows keyed by the collection id, pages and their versions, records, go-aheads, the plan, and a job claim table whose unique index keeps a Factory job in at most one workstream. factory_job joins the ref kinds in the SQLite CHECK, the workspace model and the wire validator, with its factory id on the ref. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * workstream: domain package for the workstream store Stage, numbers, plan, pages with versions, records and go-aheads, written over the collections store's transactions. Every rule the Workstreams plan (WP-1) gives the store lives here behind plain Go types, so the wire never reaches into the tables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: serve the workstream methods from the store Every workstream.*, page.*, record.* and goahead.* method now answers from the store, behind bounds checks at the wire's one door, and hello announces workstreams. Create makes the collection, the row, an Overview page and one bare agent (no first turn), and a repeated client id returns the first. Every write sends workstream.changed. Stage and numbers come from bounded Factory queue reads and the agent's recorded spend; anything unknown is left out rather than sent as zero. Collections mark workstreams, refuse to delete one, and refuse a job another workstream holds with in_other_workstream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: change entry for the workstream store Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: workstream locks unlock from a defer, factory reads are guarded The guard laws want every lock released by a defer and every goroutine guarded; the workstream Factory cache and batch reads now are. Update, mark done and reopen answer with the same live numbers as get, and workstream text refuses control characters (prose keeps newline and tab). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * workstream: page.get renders markdown through pagedoc The engine keeps one page renderer: page.get's markdown now comes from pagedoc.Markdown, which matches the desktop app's renderer byte for byte, and the store's own renderer is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: a panicking Factory read still finishes its flight; writes keep the first start A queue read that panics inside its guard now closes its flight and caches the failure, so waiters are released and a later read tries again. Update, mark done and reopen persist the first observed start like get does and announce one workstream.changed for the whole change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…e) (#2288) * tests: the wire contract's workstream examples as engine testdata The examples, field tables, enums and error codes the Workstreams wire contract carries, one file per method and event, with the extractor that regenerates them when the contract changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tests: workstream answers through the wire keep the contract's shapes Each method's example params go through the real wire handler against a seeded store; each answer must have its example's keys at every level, the field tables' JSON kinds and enum words, no nulls and integral integers. Every engine-sent error code has a call that checks its code and detail keys. A method, event or error code the contract gains without a case fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: change entry for the workstream wire conformance test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: the conformance change entry names its pull request Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tests: conformance replies carry every illustrated array element A reply array shorter than its example no longer skips the elements the example illustrates (a plan's draft item, an unattributed page version); page.delete must return exactly the seeded parent and descendant. Request writes to the wire are bounded like replies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Brings the integration branch up to app/v3 (28 commits: Factory spend and budget rows, one engine per home, memory off by default, the app delta window, process-group reads on macOS, and their tests). No conflicts; the three files both sides touched (cmd/codeaf/app.go, internal/appwire/server.go, docs/design/app-v3/PROTOCOL.md) merged line-disjoint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ons read by jobs (#2290) * workstream: deleted pages are kept and restorable; pages read by version Schema 3 (unreleased) gains deleted_at and the delete that removed each page. Delete marks a subtree; restore brings back what that delete removed, placed last under a live parent or at the root. A page reads any saved version. Read-by-jobs defaults on for pages titled Spec or Decisions at creation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: page.get by version, page.list include_deleted, page.restore of a deleted page The wire handlers serve the wire contract's v0.1 page additions and apply the Spec/Decisions read-by-jobs default when page.create leaves it unset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tests: conformance covers the v0.1 page params and example pairs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: change entry for workstream page versions and restorable delete Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ges) (#2289) * workstream: agent pages stay the agent's, lookup by agent, markdown import The store now refuses, inside the save transaction, an agent write to a page the person owns, and an agent create of a page it would not own, so a page made the person's between a read and a save is still refused. ByAgent finds the workstream whose agent is a conversation. pagedoc.FromMarkdown turns markdown into the editor JSON the page renderer reads, live-block markers included, and the contract's example round-trips byte for byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * session: a workstream agent's prompt section and six tools behind a door A conversation handed a WorkstreamDoor carries workstream_read, propose_decision, propose_question, propose_jobs, propose_goahead and write_agent_page, and a short prompt section naming its workstream and the rules: it proposes and the person keeps, it writes only its own pages, it never invents numbers and it starts nothing. Arguments outside each schema are refused before the door is touched, propose_jobs only appends drafts and retries a version conflict once, and a go-ahead proposal is a proposed decision that grants nothing. Without a door the belt and the page are unchanged, and task nodes, errands and clarifications never carry it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: give a workstream's agent its door, and re-read spend after its turns The app sets the workstream door on the conversation whose created-by is workstream and on no other; the door finds its workstream at each call, since the agent is opened a moment before the workstream row exists. Its writes are attributed to the agent and send workstream.changed, and a workstream agent's settled turn sends one too so its spend is read again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * manual: what a workstream's agent can do Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: change entry for the workstream agent's tools Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: gofmt the workstream agent door test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * workstream: agent writes check done in their transaction; markdown import is linear An agent's record or page write now refuses a done workstream inside the same write transaction, so marking done between the agent's read and its write leaves nothing behind. Imported list items always begin with a paragraph, and unmatched backticks or brackets are consumed in one pass instead of rescanning the rest of the line for each one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * session: workstream tools follow the argument and retry laws The prompt's workstream brief is a snapshot that never touches the store, so a re-render under the session lock cannot wait on it. Arguments decode only through decodeToolArguments, a repeated key is refused at any depth, every argument refusal is marked, and the plan's one retry is written without a counting loop. A go-ahead's proposed cap is dollars, excused from the whole-number law in its own file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: keep each workstream agent's brief current without reading the store The app remembers each workstream's name and why by its agent when it creates, reads, updates, marks done or reopens one, and when the agent's door resolves it, so the prompt reads that snapshot. A done refusal from the store reaches the agent as the workstream being done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: workstream agent tools refuse repeated keys; done is checked in the write Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * session: import the workstream types in the validation test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * workstream: only an explicitly attributed agent proposal is refused when done The done check in ProposeRecord read the defaulted actor, so the wire's unattributed record.propose on a done workstream, which has always landed as a proposal, was refused. It now reads the attribution the caller gave, and the agent's door keeps passing its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * session: the workstream agent keeps its "Where it stands" page current The app shows the agent's summary on the Overview from the agent page titled exactly "Where it stands". The prompt and write_agent_page now name that page and ask the agent to keep it current with what it was updated from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * session: a shorter Where it stands rule, and room for it in the prompt budget The rule now fits in one short line, and the workstream section's own budget test allows 1000 bytes for the worst-case name and why. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tests: the conformance seed lends the agent the Overview for its one save The store now refuses an agent write to a page the person owns, so the seed that reproduces the contract's agent-saved Overview hands the page to the agent for that save and back again, keeping the agent's attribution and version. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tests: page version and restore fixtures write as the agent only on its pages The store now refuses an agent write to a page the person owns, so the version-read and deleted-tree fixtures that save or create as the agent make those pages the agent's first; what they assert is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…eaf leg 25m) (#2296) * tests: the conformance busy case waits 200 ms for the lock, not 10 s The busy error case held a real writer on a second handle and let the backend's store wait out the full 10-second SQLite busy timeout. That one subtest was 10.0 of the workstream tests' 11.2 seconds in cmd/codeaf. It now sets PRAGMA busy_timeout to 200 ms on the backend's own single connection, reads it back and fails unless it took, then holds the same real writer. The refusal still travels SQLITE_BUSY -> ErrBusy -> wire busy through the real store and dispatch. Production keeps its 10 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci: the codeaf touched leg gets a 25-minute package timeout cmd/codeaf runs unsharded (sharding it failed in #1734). Its serial suite measured 1065 s (17.8 min) on the 4-vCPU runner on 2026-11-03, 1053 s of it in tests that predate the workstream work, so the leg hit the Makefile's 15-minute timeout and every test after the cut went unchecked. The base branch's own runs time out the same way. Only the codeaf leg passes --timeout 25m; tui3, session and rest keep the Makefile's 15m. A timeout is still red. docs/rules/ci.md records the exception and that it goes once the suite is back under 15 minutes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: change entry for the codeaf leg's timeout Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ow agent ids, goahead proposals that grant when kept (#2297) * workstream: draft size/budget/source, and go-ahead proposals that grant when kept Draft plan items keep an optional size, budget and source. A goahead record carries its proposed scope, cap and end; keeping it grants exactly one go-ahead in the same write, dropping a kept one revokes it. Schema 3 gains the record columns in place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: workstreams wire v0.2 (row agent ids, page sources, goahead records) and conformance Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * session: the workstream agent proposes go-aheads as goahead records and sources its draft jobs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * changes: number the workstreams wire v0.2 entry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… chat resume skips the per-chat database scan (#2303) * workspace: SQLite refuses deleting a workstream's collection, for any writer An engine that opened collections.db at schema 2 before another engine migrated it keeps its cached version and its old Delete, which has no workstream check: its DELETE FROM collections would cascade away the workstream, its pages, saved versions, records, go-aheads and plan. A BEFORE DELETE trigger now refuses that delete in the database itself. New and migrated files get it with the schema; schema-3 files from earlier integration builds gain it (IF NOT EXISTS) inside their next write. Plain collections delete and cascade as before; Store.Delete keeps its message. The deleted-pages purge test deletes the workstream row before its collection, since the collection alone can no longer be deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chat: resume asks collections.db about workstream agents only for empty chats v3EmptyUnassignedSession opened collections.db before its cheap local checks, so resuming in a project scanned the database once per saved conversation, including every conversation somebody spoke in. The ownership question now comes last, for folders the local checks already found empty, and still keeps a folder whose owner cannot be read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * changes: entry for the workstreams stability fixes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…sions pages read by jobs (#2310) * workstreams: size limits on plans, pages and records (wire v0.2.1) Draft text was bounded only by its own length, plans had no count or encoded-size bound, and a workstream could accumulate pages and records without limit, so every list read materialized all of them. The store now refuses, inside its write transaction and before anything is written: draft text over 80,000 bytes; a plan over 500 milestones, 500 waves, 5,000 items or 4 MiB of encoded milestones and waves; a 5,001st page (deleted pages included) or record. The new ErrLimit names the bound and answers too_large on the wire; the plan_set check refuses the counts and draft text early. The conformance testdata is regenerated from the contract (too_large on plan_set, record.propose, record.supersede). The agent manual states the limits and that new agent pages titled Spec or Decisions are read by jobs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * workstreams: agent pages titled Spec or Decisions are read by jobs, as page.create makes them Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * workstreams: control characters stay bad_params past the draft text bound; the manual separates the tool's draft limit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * workstreams: tests for the size limits and the agent's Read-by-jobs default TestStabEm1AgentSpecAndDecisionsPagesAreReadByJobs: agent pages titled Spec or Decisions (any case, surrounding space) are read by jobs, others not; updates keep the flag. TestStabEM4*: plan bounds through the real wire and the store, exact bounds accepted and one past refused with too_large and no event; page and record counts (deleted pages included) refuse wire and agent writes; plan_set and record.propose too_large conformance cases; early wire refusals keep bad_params for control characters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: change entry for the workstream size limits Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Brings the integration branch up to app/v3 (29 commits: job step labels and conditions, step cost as the sum of its rounds, spending kept across reruns, closed issues leave Backlog, one way to build a crew watch, run workers act as their task, one model per role, live lines per call, and their tests). No conflicts; the three files both sides touched (cmd/codeaf/app.go, internal/appwire/server.go, internal/session/session.go) merged line-disjoint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since app/v3's "A job keeps its earlier spending when it runs again", a job's Cost is only its current run and LifetimeCost holds every run. The workstream's spend summed Cost, so it fell when a member job ran again. It now takes the larger of the two, which is LifetimeCost from a current factory and Cost from an older one that does not send it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…loat Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e small changes, wake.* methods) (#2319) * workstreams: schema 4 adds wake tables by an additive migration Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * workstreams: wake records, settle under autonomy, apply/undo/not now Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: wire wake.list, wake.apply, wake.undo, wake.not_now and the one-shot wake turn Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: the workstream agent wakes each morning and on member job events; wake costs count toward spend Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: the watching agent's change entry names its pull request Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * workstreams: undo refuses drafts and pages changed since; morning wakes only for today's slot; wake calls carry their purpose Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: the protocol document lists the wake methods Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * workstreams: wake undo keeps a later rename and tolerates sibling reorder; replay knows the wake's role Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tests: wake.list fixture follows the contract's money form Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * workstreams: the wake call has a ledger owner; the wake queue unlocks from a defer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ma 5 (#2320) * workstreams: schema 4 adds workstream_sources by an additive migration Schema 3 stays readable without writing; the first write creates the table and moves to 4 in one transaction, keeping every existing row. Schemas 1 and 2 migrate straight to 4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * workstreams: sources store (file, repo, issue, link, kept) with strict path and URL checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: workstreams wire v0.3: source.list/add/update/remove, create takes sources Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: the workstream agent reads sources and proposes new ones for the person to keep Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: change entry for workstream sources names its pull request Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: the app protocol lists the source methods Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…, suggest_page_edit, schema 6) (#2318) * workstreams: page text blocks, edit anchoring and keeping by code point Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * workstreams: suggestions store: list, keep one or all as one version, drop Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: workstreams wire v0.3: suggestion.list, suggestion.keep, suggestion.drop Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: the workstream agent suggests edits on the person's pages (suggest_page_edit) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: change entry for workstream suggestions Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: number the workstream suggestions change entry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * workstreams: keeping an edit leaves other text nodes' fields intact; empty marks equal no marks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: the protocol lists the suggestion methods Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * workstreams: schema 5 adds the suggestion tables after the watching agent's schema 4 Files at 1-4 stay readable as they are (suggestion reads answer empty); the first write applies every missing step in order inside its transaction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: the suggestions change entry names schema 5 and wire v0.4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * workstreams: schema 6 adds the suggestion tables after Sources' schema 5 Files at 1-5 stay readable as they are (suggestion reads answer empty); the first write applies every missing step in order inside its transaction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: the Suggestions addition counts 36 methods and eight agent tools; change entry names schema 6 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: the suggestion prompt rule fits the workstream section's 1000-byte budget beside the Sources rule Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Brings the integration branch up to app/v3 (6 commits: the stored repository list served with a background refresh, step conditions read the change's files, step and My pipeline AI count as Factory AI, notes and the completion guard share one command outcome, opening a job doesn't count as a person's touch, a budget raise isn't refused by spend progress). No conflicts; the three files both sides touched (cmd/codeaf/app.go, internal/appwire/server.go, docs/design/app-v3/PROTOCOL.md) merged line-disjoint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eld conversations are kept (#2334) * A client can attach tools to a conversation over the app wire agents.tools_set attaches, replaces or removes one named set of tools on a conversation; agents.tool_call carries each model call to the client and agents.tool_result answers it. The hello says agents.client_tools. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Client tools ride at the tail of a conversation's belt A conversation with a client tool door offers the client's tools after every built-in, with each set's instructions on its own page and nothing added when no set is attached. Tools follow their declared category, a built-in name wins, and child tasks and discussions get neither tools nor instructions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * A conversation any collection holds is not blank-chat litter Launch cleanup, the blank-chat sweep and the background places sweep keep a never-spoken conversation that a collection references, reading the collections database read-only and keeping the folder when it cannot be read. Opening such a conversation by id after a restart restores its owned, model, archive and spend facts from its metadata. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * codeaf app sends client tool calls to the connected app The app backend keeps each conversation's client tool sets for as long as the connection lasts, emits one agents.tool_call per model call and waits for the answer, its deadline, the turn's end or the connection closing. Call ids are stable for a replayed call, and a late or repeated answer is ignored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Change entry for client tools and collection-held conversations Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The client tools manual page is found by its own name Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Client tool registry follows the guard and name laws The registry's sender runs under guard.Go, every registry lock is released from a defer, and a test name no longer spells the old product name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… (schema 7) (#2336) * collections: schema 7 keeps every collection and Factory job ref, drops development tables The collections store now writes schema 7: collections and memberships in the generic shape (factory_job refs with their factory, five-column UNIQUE), and nothing else. Versions 1-7 are read as they stand; the first write migrates inside its own transaction. Schemas 1 and 2 are copied into the generic tables; schemas 3-6, written only by development builds, lose their extra tables, indexes and triggers while every collection, membership and ref stays. A failed first write leaves the file as it was; 8+ is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: the engine keeps only the generic pieces of the Workstreams plan Workstreams move into the app, so the engine drops everything that existed only for them: the workstream store and page renderer, the wire methods and hello features, the agent's built-in tools and prompt section, the morning wake scheduler and its replay purpose, the conformance fixtures, and the old launch-cleanup hook. Removing the agent door also removes the copy of it that Fresh and Open carried. What stays is generic: collections hold Factory jobs (the resolver moves to appcollections_factory.go under generic names, with its own tests), client tools, and collection-held conversations that survive cleanup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: protocol and manual keep Factory refs and client tools; workstream entries go The protocol and the collections manual lose the workstream methods, events, features and schemas, and keep the factory_job and client-tools sections. The unreleased workstream entries described features that will not ship from the engine; one entry for the generic pieces replaces them and the client-tools entry. The CI-timeout entry no longer mentions the deleted conformance case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: one change entry for the generic collections, client tools and kept conversations Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: the generic-pieces entry is written fresh, so the gate sees it as added The first version reused the client-tools entry's wording, so git paired the two files as a rename and the change-entry gate found no added entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…hown as live (#2337) * A blank conversation a collection holds is kept but never resumed Launch resumed a never-spoken conversation that a collection references, ahead of the last spoken one, because "kept" and "spoken" shared one predicate. The emptiness check now reads only content; references keep a blank out of both the resume and the reuse piles, and every removal path still refuses it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Collections show a held blank conversation as live after a restart The resolver built its view from agents.list, which leaves named blanks out, so collections.open and collections.expand called them deleted. A missing id is now looked up once on disk before it is called deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: held blanks are neither resumed nor called deleted Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Change entry for #2337 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * A permanently deleted conversation stays deleted in its collection The on-disk fallback found the metadata a permanent deletion keeps, so the resolver would have shown a deleted conversation as live and allowed a relink to it. A row pending deletion, or a folder with the deletion marker, now stays deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Only a conversation member uses the saved-folder fallback A blank found on disk shared the listing cache that task and process members use to find their owner, so a collection read could open a crew member's conversation to look for a child that does not exist. The fallback now serves conversation members only, from its own cache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Brings the integration branch up to app/v3 (4 commits: pairing QA fixes for failures, approvals, removed computers and the devices loop; a person's pause survives a lost heartbeat reply; pairing follow-ups where app and pair approve settle a lost reply and chat rows say a take is running; the plugins desktop protocol over the connect layer). One conflict, in cmd/codeaf/app.go: the appBackend struct keeps the client-tools field beside app/v3's plugin pause, Ollama and auth fields. internal/appwire/server.go (both optional hello features), internal/session/connect.go and docs/design/app-v3/PROTOCOL.md merged line-disjoint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Client tools: a tool call names the message its turn answers (source_message_id) agents.tool_call gains an optional source_message_id: the durable id of the message the running turn answers, the same string as Entry.message_id on that message's chat entry. It is captured when the turn starts and carried on the turn's context, never sampled from the app's current turn, so it survives an engine restart and a replayed call. A stopped turn asked again carries its recorded message; a carry-on after a quit or close carries the message that opened the interrupted run only when the record names it exactly. Turns the engine wakes on its own notes carry none. Crew provenance is unchanged. The hello feature agents.client_tools.source comes with agents.client_tools; turn stays the process-local counter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Client tools: tests for the source message on tool calls A call names its own message (and a later message its own), the same id after reopening the journal, on a resumed stopped turn, on an exact carry-on and on a fresh-registry redispatch; note-woken turns carry none; a steer never moves it; the field is omitted when empty and the feature rides with client tools. Every wait in the new fixtures is bounded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Change entry for the client tool source message Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Client tools: a carry-on after a quit or close names no message Naming the interrupted run's opening message needed a run id on the person's line, which changes what a person's line records. A carry-on turn is one the engine starts itself, so it now carries no source_message_id, like every other turn the engine starts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…sses (#2360) * Client tools name their answer cutoff a wait, not a leaf's room The pending client-tool call held the instant the engine stops waiting for the client's answer in a field named `deadline`, sized from milliseconds. The leaf-room law in internal/exec reads any such write as a hand-sized leaf room and failed on it. It is not one: the limit comes from the client's registered tool set (or the call's own in-range DeadlineMS), not from the subharness table, and routing it through SubharnessFor would change it. Rename the field to `answerBy` and the local limit to `waitMS`. The effective limit, the DeadlineMS sent to the client, the wait timer and the expiry checks are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: name pull request 2360 in its change entry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Brings app/v3 up to db138db (130 commits), including Factory budget work (#2353, #2388), desktop Library access to shared knowledge (#2369) and test-home helpers. Two conflicts, both kept on both sides: - cmd/codeaf/app.go appBackend: clientTools beside app/v3's libraryWatch and libraryInboxMu. - internal/session/sweep.go: app/v3's moving-folder sweep (#2350) beside the caller's keep rule; sweepSession still takes keep. #2350's new launch test calls SweepHomeContext with the keep argument the branch added (nil). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2352's test calls SweepPlacesContext with app/v3's four arguments; the branch's signature also takes the keep rule, so the session test binary did not compile after the merge. The test keeps nothing (nil). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* turn.tool carries the conversation's live spend so far A running turn said nothing about money until it ended: Agent.spent_usd is the figure saved at turn end, spend.changed is the machine's day, and the live meter was only answered on request by agents.budget.get. Every turn.tool event now carries an optional spent_usd, read from the same live meter after the emitter has released its locks, so an app can show what a run has cost while it is still running. Zero or unknown readings and history rows omit it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Change entry for the live spend on turn.tool (#2413) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…less than it charged (#2414) * A resumed conversation restores its late receipts, so it never saves less than it charged A late provider receipt (a cut stream priced after the fact) is banked outside every turn seal and leaves only its call line with an arm mark. Replay summed usage seals alone, so after a restart the meter forgot every late charge, and the next turn could stamp meta.json below the total a late receipt had already saved (.25 + .125 saved .375; resumed, a .01 turn saved .26). Replay now restores a call line that carries an arm (reconciled or hedge) as one call and no turn, exactly as Agent.reconciled banked it, and Digest.Spend counts the same lines so a meta row rebuilt from the transcript agrees. Ordinary call lines stay evidence and are never billed; Digest.Usage still lists only usage lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Change entry for restoring late receipts on resume (#2414) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * A late receipt that arrives after Close keeps its line, so resume still restores it Close lets pending receipt accounting finish after quit, and the closed journal already admitted usage seals for that reason, but it refused the arm call line a late receipt writes. The charge then reached meta.json and vanished on resume again. The closed-file exception now admits a paid arm call line too; ordinary call lines stay refused after close. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Manual: say late receipts are restored with the smallest wording change The broader rewrite moved the manual's question ranking (TestTheChatManual- AnswersTheQuestionsPeopleAsk); keep the original sentences and add the late-receipt exception beside them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#2416) * factory: keep job outcomes in a bounded record and answer them from a cursor A job that merged, finished, failed or needs a person is recorded at the store write, beside the live notifications and under the same lock, in its own job-outcomes.json: a sequence of its own, the newest 2,000 kept, and a floor below which a reader resets instead of skipping. The live record, its ids and /v1/events are unchanged. An older binary writing the same folder drops the outcomes_through marker, which the next commit turns into a gap. GET /v1/job-events answers the outcomes after a cursor for the jobs the caller may see, with an optional long poll of up to 15 seconds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app: factory.events_since reads job outcomes and factory.event pushes them factory.events_since {factory, after?, limit?, follow?} answers the Factory's job outcomes after a cursor, or a reset when the cursor is absent, older than what the Factory keeps, or crosses a gap. With follow, one follower per factory long-polls the Factory and pushes each later outcome as factory.event. A Factory older than the route answers supported false. Hello feature factory.job_events, capability job_events. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * changelog: name the pull request Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * factory: compare outcomes against a deep pre-update copy; make the detach test deterministic The store's update snapshot shares the stream, merging and follow-up with the item the callback edits, so an in-place change could hide a needs-you change from the outcome comparison. Outcomes now compare against their own deep copy taken before the callback; live.Changes keeps its snapshot as it was. The follower test now drives its retry wait and observes the outstanding long poll instead of racing a real backoff; a follower whose connection is gone ends at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…2417) * Remote door: client tools cross to the connection that set them A holder that forwards client tools says so in its welcome. A connection sends its whole snapshot of sets with ClientTools.Set; the holder's conversation hub puts them on the belt, sends each call to the owning connection only and takes the answer back with ClientTool.Answer. A connection that leaves (detach, torn pipe, close, failed write) loses its sets and its pending calls settle at once with the disconnected answer; a call nobody answers ends at its existing wait. A roaming client re-sends its sets after a redial. The protocol version is unchanged: an older peer never sees the new calls or frames. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * App: client tools reach conversations held by another codeaf or computer agents.tools_set on a conversation the app joined through another host now forwards the agent's sets to that holder when it supports it, and the app pushes them again every time it holds the conversation. Calls come back as agents.tool_call on the app connection that set them. An older holder keeps the existing bad_scope refusal. A chat held on another computer stores its sets and uses them once it runs here again. Hosts without an app give each conversation the forwarding hub, and the app hello adds agents.client_tools.remote. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: change entry for remote client tools (#2417) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Client tools across the remote door: review fixes One sender per owner connection drains its pending calls and skips any call already settled or past its wait, so a stuck write holds one sender and no queue. Requests go through the client's ordinary call path. Each client carries a random owner token: a redial that arrives while the old connection is still attached moves the sets to the new connection instead of colliding with itself. A snapshot replays only onto the conversation it was acknowledged in. A forwarded call waits for an in-flight publication only as long as its own context and wait allow. Every new lock unlocks from a defer, and the chat manual keeps its one bounded section. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Remote client tools test: gofmt the instructions-limit case Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* factory: a job can wait on other jobs' merges
Jobs carry the jobs they wait on, the ones not yet satisfied, whether their
waits were released, and why a parent blocks them. A parent satisfies its
children when its pull request merges or it ships with nothing to merge; one
that fails or ends unmerged blocks them until it is fixed. The job read shows
waits_on, waiting_on and blocked; Pick up issues never starts a job that waits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* factory serve: set a job's waits and release them when parents merge
PUT /v1/jobs/{id}/waits_on sets or clears a Backlog job's waits, refusing
unknown jobs, the job itself, cycles and parents that ended unmerged. A
scheduler in the service rechecks waiting jobs at start, after folder changes
(at most every two seconds) and every minute, and releases a job once every
parent is satisfied. It never starts a job; a person's Start ends its waits.
/v1/about lists the waits_on feature.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* app wire: factory.waits_set and factory.features
factory.waits_set sets the jobs one job waits on, outside the job-action table
because its list names parents, not jobs to act on. factory.features reads what
a connected factory serves from its about answer. The engine's factory
capabilities say waits_on. An older factory answers factory_too_old.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: waits ordering, blocks, refusals, restart and the wire
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* changes: factory job waits entry
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* factory: waits hold against closed issues, intake races, hidden parents and resets
A closed issue ends its job unmerged; only a published no-change receipt
satisfies children without a merge. Intake re-checks pending waits under its
own admission lock. Parents the caller cannot read are refused as unknown,
and a parent in another repository is named only by its job number. A start
through the service retires pending waits at once, so a Drop and Bring back
before the next scan cannot revive them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: closed issues, intake race, hidden parents, durable starts and the scan loop
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: waits read test calls FromSnapshot with its caller; the queue check reads the plain queue
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: the waits current read fixture names the job the way the factory does
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…y key; factory.job_context_set (#2415) * Factory keeps a new job's group, milestone, context and starting budget POST /v1/jobs and /v1/issues take optional group and milestone labels, context attachments outside the description limit, and a budget from the standard steps. They are checked before any issue or job is made, stored on the job, and the job read carries the labels and each attachment's title and source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The plan step reads the context attached to its job Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * factory.job_create takes open_issue, group, milestone, budget, context and key Each is announced in factory.capabilities. The key travels as the create request's Idempotency-Key, so a retried create makes one job. An older factory that refuses a new field answers conflict with reason factory_too_old and nothing is made. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Change entry for factory.job_create extras Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Context attachments are their own write: factory.job_context_set factory.job_* params carry no lists, so context leaves factory.job_create. factory.job_context_set {factory, job, expect, context} replaces a job's attachments while it has no run (POST /v1/jobs/{id}/context, hello feature factory.job_context, capability job_context). The plan step that will actually run reads them; a disabled, excluded or skipped plan no longer hides them from the first conversation step that runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Change entry names factory.job_context_set Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Count only the Factory writes in the new create and context tests attachStub's own probe is a GET; the tests now count the one write each call sends. The change entry's title fits on one line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * A plan skipped after earlier steps hands its context to the next conversation When the plan step is skipped without opening a conversation, the first conversation after it reads the attachments, once. The chat manual describes making a job with an issue, labels, a budget and a retry key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * A job outcome carries the group the job was made with factory.event and factory.events_since already had a group field; it is now filled from the job's group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… GitHub connection (#2419) * Factory lists a repository's issues through its GitHub connection GET /v1/repos/{owner}/{repo}/issues?since=&page= reads GitHub's issue list in every state, oldest update first, as the caller the Factory already uses for issue search, so private repositories work. Pull requests are left out of issues but counted in rows and newest, so a caller's cursor moves past them. Bodies are cut at 8,192 bytes on a rune boundary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * factory.repo_issues reads a repository's issues through the Factory Hello feature factory.repo_issues and capability repo_issues. A repository the caller cannot see is not_found; GitHub's spent rate limit is busy with detail.retry_at; a Factory without the route is conflict with reason factory_too_old. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Change entry for factory.repo_issues Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * factory.repo_issues uses the shared factory_too_old reason Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…old runs past it (#2423) * factory: group policies with allowances and a stop A job may carry an opaque group. A group's policy, kept in the factory's folder, lists allowances (jobs, a dollar cap, an end) and a stopped flag; its spend counts every run of the listed jobs. Pick up issues leaves grouped jobs to their policy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * factory: start released jobs inside an allowance and hold runs past it The waits scheduler starts a released grouped job on its own only when an active allowance lists it and has room for its budget; otherwise it stays startable with the reason. Beside the job budget stop, the lease desk holds a run the policy started once its allowance ends, is spent or no longer lists it, and holds every run of a stopped group. A person's start or Continue makes the run theirs. Stopping a group stops its running jobs after their current step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * app wire: factory.policy_get, factory.policy_set and a job's group Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: allowances, headroom, ends, stops, person runs, spend and the wire Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * factory: policy gate reads only listed jobs, never cuts an admitted step, compares money in micro-dollars The gate checks a stopped group first, then reads only its allowance's listed jobs through a read-only store read, cached for two seconds, never listing the store under an item lock. A stopped group arms Stop after this step on a step the service already admitted and holds only a new admission, decided from the service's own admission record rather than worker clocks. Every policy money decision compares whole micro-dollars. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: no lock under listing, bounded reads, stop interleavings, skewed clocks, money edges, intake Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * changes: factory group policy entry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: the person-doors policy test builds its handler with an answer door Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: policy app tests read a job as the factory names it; reads are not owner-guarded Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * factory: a job's group is the one job creation already keeps The job-creation change made group a field of the job, the create request and the job read. This step uses it and drops its own copy, its create check and the tests that duplicated job creation's own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…om (#2429) * A job made from a split records the job it was split from factory.job_create takes split_from, a job of the same Factory the caller can see (capability create_split_from). The Factory checks it before any issue or job is made, stores it on the new job, notes it in the job's activity and returns it on every job read. The parent job is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Change entry for split_from Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * split_from tests count only the Factory writes and read a job as the API spells it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * split_from older-Factory test compares the refusal's message Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Brings app/v3 9a87ed6 (30 commits) into the integration branch. No textual conflicts. docs/rules/ci.md keeps both sides' branch lists: PRs into dev, app/v3, santos/dev2 and workstreams/main, pushes to dev, santos/dev2 and workstreams/main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…saved jobs app/v3 now fills a job's missing author access when Get or List reads it, which can wait on a GitHub lookup and write the job. Bookkeeping reads use the saved snapshot instead, as the lease desk and record already do: policy allowance reports and group stops, waits graph checks and release scans, the split_from parent check, and the outcome cursor's per-job visibility. A transient lookup failure no longer shows up in a waits check, and reading these never changes a job. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every request first settles due jobs with a hydrating listing, before routing, so a whole-request count of author lookups also counts that. The policy, outcome-cursor and split_from tests now call their handlers directly and still fail if those reads go back to Get or List. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Factory: GitHub's spent rate limit on a visibility check answers retry, not "repository not found" The repository visibility check read only GitHub's status, so a spent rate limit (403 with no remaining quota or a Retry-After, or 429) became a denial cached for ten minutes and every repository route answered 404. The check now keeps the headers, never caches a spent limit, and the repository routes answer 429 with retry_at in the issues read's existing shape. A source poll that fails on a spent limit says "rate limited". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Factory: a keyed request is never answered from a cached 5xx or 429 The idempotency cache replayed the first answer under a key for 24 hours, including server errors and rate limits, so a retry after a transient failure could never succeed. Those answers are no longer kept or replayed, including rows an older build stored; successes and other refusals still replay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * changes: entry for the Factory's retryable transient failures Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * changes: the spent-limit condition applies to both 403 and 429 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Factory: keyed replay asks api.WorkerResendable, as the worker rule does The taxonomy law keeps HTTP status comparisons out of ordinary packages; the Factory's existing helper already names the try-again answers (5xx, 408, 429). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * changes: 408 is a try-again answer too Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* session: an abandoned turn's paid steps are sealed, so a restart keeps them A turn let go of at the second stage wrote only its abandoned line, which is evidence and never billed, so the conversation steps it had paid for were on the live meter but in no seal. After a restart the replayed meter missed them and the next stamp saved a lower total. The displaced turn now writes its own step tally as a journal-only seal when it leaves its tool batch (no duration, no turn-ending acts). Errands keep their own lines; the abandoned line stays evidence. Two ordinary exits that returned without any seal (a settle turn at its bound, and a final stage report) now seal through sealTurn like every other ending. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * session: tests for abandoned and unsealed turn endings across a restart Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * changes: entry for abandoned-turn step seals Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * session: the settle-bound and final-report exits seal in the journal only A full sealTurn on those exits could, after an abandon, end a replacement turn's seal watch, move its notices and add duration. They now write the step seal the abandoned exit writes: the money, no duration, no turn-ending acts. The digest regression closes its agent before reopening the journal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * changes: the extra exits write journal-only step seals Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…h, count and spend (#2446) A conversation proposal's worker opens on its own work, with the person's message after it as the conversation's background. Run workers' plan CLI writes refuse duplicate titles, tasks more than two levels below the run's root, and more than 20 counted tasks per run. Every ordinary run has a whole-tree dollar ceiling (the lower of the conversation's remaining limit and the per-task limit), and reaching the per-task limit stops the tree as a budget stop. Countdown-started proposals joining a live run obey the same bounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings app/v3 a2a444a (10 commits) into the integration branch. One conflict, .github/workflows/ci.yml: app/v3 #2443 now shards cmd/codeaf in the touched gate, which replaces the branch's unsharded 25-minute codeaf leg (its own comment said to drop it once the suite fits 15 minutes). The branch keeps its workstreams/main pull-request and push triggers. docs/rules/ci.md keeps the branch list and drops the 25-minute exception. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er their own task, run subtasks keep their parent (#2452) A conversation proposal's run is built from its own title, work, deliverable and done-when only. A run worker's plan CLI adds only under its own task, five tasks per worker, so the depth bound binds; a hand-off joined into a live run reads its own assignment and the wake lists only what the worker dispatched. tasks.list keeps each run subtask's parent and group, and a stopped or set-aside run leaves nothing reading running. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings app/v3 3a6aa31 (7 commits) into the integration branch: a removed computer quits quietly, Factory poll and triage stop between test homes, and five test fixes. No conflicts, and no file changed on both sides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#2460) * Factory: any GitHub 429 is a rate limit, never "repository not found" A 429 from GitHub on the visibility check or the repository issues read was a rate limit only when it carried X-RateLimit-Remaining: 0 or Retry-After. A bare 429 fell through to the denial path: the caller read "repository not found" and the denial was cached for the visibility TTL. Every 429 is now a rate limit (busy with retry_at: the reset or Retry-After when known, otherwise one minute from the Factory clock), it is never cached, and the issues read and the poll's trouble words agree. A 403 still needs the spent-limit headers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: change entry for the bare 429 rate limit (#2460) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Tasks: each check in a run gets its own checker ceiling The checker seat's spend ceiling was meant for one task, but a run shares one crew guard across all of its tasks, so the ceiling was tallied run-wide: once one check had spent it, every later check was refused on its first call. Each run task's calls now carry its task id beside its seat, and the guard keeps the checker's ceiling per task. Every check's spend still counts toward the run's per-task limit and the day's limits, and saved crew records keep their per-seat totals. A crew without a task mark keeps its per-seat ceiling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: change entry for per-check checker ceilings (#2461) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tasks: a check's ceiling estimate reads only that check's own costs Review round: the guard raises a call's estimate to the model's last actual cost on the shared day. With per-check ceilings that let one check's overrun inflate the next check's first-call estimate past its own ceiling, so the next check was still refused before it called. A marked check's ceiling hold now raises its estimate only by that check's own last cost on the model, and each tally (run total, day, ceiling) releases exactly the amount it held. The run total and day holds keep their existing conservative estimate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…#2462) * Session: a conversation scoped to its own work gets no other-conversation context A conversation's meta.json can now store scope "conversation". Such a conversation gets no <elsewhere> block on its turns, and its tasks tool lists and addresses only its own tasks (no other conversations' landed rows, no running-in-other-windows or everywhere sections). The default, project, keeps today's behaviour. The word is written under the meta lock and read beside the turn and in the tasks tool, so a live conversation follows it from its next turn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * App wire: agents.set_scope sets a conversation's work scope without opening it agents.set_scope {id, scope} stores project or conversation in the conversation's meta.json and answers the stored word. It never opens, warms or interrupts the conversation. Engines that serve it announce agents.scope. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Change entry: name PR 2462 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Scope: own constant names, typed wire result, stale meta writers keep the scope The new scope words are WorkScopeProject/WorkScopeConversation (ScopeProject already names an answer scope). agents.set_scope returns *AgentScope as typed handlers must. saveMetaLocked keeps the stored scope when a whole-Meta writer loaded before a scope change saves with none, and project is stored as its own word so a reset survives that rule too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…end total (#2464) * Tasks: the older task road refuses duplicate work and holds a tree spend total With CODEAF_TASK_BELT=node the session-tree road bounded depth (3), children per node (20) and each node's own bill, but two nodes of one tree could hand out the same work, and the tree as a whole had no spend total. It now applies the default road's bounds from #2446: - A child whose title matches, after plandb's normalization (now exported as plandb.NormalizedTitle, one definition for both roads), any non-cancelled task in the same tree is refused with the same words, and a division naming the same work twice is refused whole. Pending proposals reserve their titles. A typed task starts a separate tree, so separate trees may reuse a title. - Every node, helper and check of one tree shares one spend tally held to the lower of crew.task_cap and the conversation's remaining limit (the same arithmetic as the default road). Reaching it stops the whole tree and the ending names the limit and how to raise it. A checkpoint keeps the tree's spent total. Two existing division tests used repeated titles as filler; their fixtures now name distinct work and keep every assertion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: change entry for the older road's tree bounds (#2464) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tasks: the older road's tree total is checked where the node limit is Review round: the first version held the tree to a new shared guard with its own stop and cancellation, which read a limit stop as a person's stop, missed paid tool receipts, could re-grant a conversation-bound allowance after a restore, and waited on checkpoint writes before cancelling. The tree total now reuses the older road's existing per-node check instead: before each worker request, the summed bill of the tree's root and every descendant is compared with the tree's ceiling, which is fixed when the root is admitted (the default road's arithmetic) and kept in the checkpoint. A node over it ends on the cost-limit ending with the configured-limit words, or the conversation-limit words when that bound it. The shared guard and its hooks are gone. TestAPartsBriefHoldsItsBoundWhateverLengthTheFieldsArriveAt sent two parts with the same title, which a division now refuses; its second part's title differs and every field stays at full length. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: older-road tree-bound fixtures deliver child reports and give plan specs an id The spend test's graph dropped every report, so the root never finished folding its parts; it now delivers child reports as production does. The work-identity test's plan write lacked an id, so plandb refused it before comparing titles. No production change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ory jobs (#2468) * App wire: announce collections.factory_job when collections hold Factory jobs An engine serving collections now also announces collections.factory_job in hello.features. This wire already checks factory_job refs (a Factory job by factory and number) and passes them to every collector, but a client had no way to tell such an engine from an older one that announces only collections and refuses the kind with bad_params. A client can now check the feature before it adds a job, instead of finding out after it made one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Change entry: name PR 2468 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * App wire: an engine without collections.factory_job may refuse the kind, not must Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…#2465) * Models: a fallback model is for that run only, never the saved choice On a local launch whose configured model is not listed (no key for its provider, say, with only a local Ollama running), the terminal picks the first listed model so the conversation can run. It skipped the profile's talk row, but it switched through the ordinary model setter, so the fallback became the conversation's saved model: the agent reopened on it (also in the app, where the configured provider worked), and errand routes that fall to the conversation's model followed it. The chat manual already said automatic replacements do not overwrite a saved preference. The conversation now has a run-only model door (session UseModelForNow, carried over the linked-local wire, older engines keep the old setter). Requests run on the fallback while the saved model, provider, identity and main/fallback pair keep naming the configured choice; the app reports the configured model. The terminal's availability reconcile, the implicit credit-default follow, the move after disconnecting the running model's service, and the linked-local source refresh use it, and every later reconcile returns to the configured model as soon as it is listed again. An explicit choice, including choosing the fallback that is already running, and a fresh service connect still save as before. A chat continued from another computer keeps its existing replacement rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: change entry for run-only fallback models (#2465) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Models: the run-only switch is asked off the update loop; renames, queued words and new chats keep the saved choice Review round: - The terminal asks the engine for the run-only model through its ordered off-loop command, as every engine door must; sends wait for the acknowledged switch, and an explicit pick queued behind it supersedes it. - Renaming a service relabels the saved choice and a run-only model separately, so a rename never saves the fallback. - An explicit choice clears a run-only model word still queued for the next request. - start_chat with no model gives the new chat its parent's configured model; the new chat applies its own availability reconcile. - The implicit credit-default follow is restored as it was: it is the build's default moving, not an availability fallback. Existing tests gain only the acknowledgment step the asynchronous switch needs; no assertion changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Models: parked sends and deferred renames wait for their own chat's model switch Review round 2: - A parked message (text and attachments) stays queued while its chat's run-only switch is pending or refused, and is sent once on the acknowledged model; pending switches travel with the chat when the window moves to another. - A deferred service rename is bound to the chat it was made for and is retired by a later explicit pick, so it never saves into another chat or over a newer choice. - Two new test fixtures gain the provider base URL their sessions require. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Models: a send waits for its chat's pending model switch instead of the window holding it Review round 3: holding sends at the surface while a run-only switch was unacknowledged kept missing send roads (a send deferred behind a replay, a hidden chat's queued send). The surface no longer holds anything: it shows the replacement at once, and every engine call that starts a turn (a plain send, an attachment send, a follow-up, an errand send) waits inside its own command for that agent's switch still on the ordered door line, so the engine always sees the switch before the message. A refused switch still lets the message go; the next reconcile retries. A hidden chat reconciles when it comes back to the front, as before. Existing tests keep only the acknowledgment steps the asynchronous switch still needs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Models: a takeover resume waits for its chat's pending model switch Review round 4: resuming a turn stopped by a takeover started the turn without waiting for the chat's run-only switch still on the door line. It now waits inside its command like every other turn start; a cancelled wait leaves the resume for later, a refused switch still allows one resume. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Integration branch for engine changes the desktop app's Workstreams feature needs. Draft: do not merge.
The Workstreams feature itself lives in the desktop app; the engine stays the AI harness. After an architecture
correction on 2026-11-03, every workstream-specific piece that had been built here was removed (#2336). What remains is
generic and useful to any client:
(
agents.tools_set); each call reaches that client asagents.tool_calland is answered withagents.tool_result.Tools exist only while the client is connected; disconnect, timeout and turn end answer the model with an error.
Hello feature
agents.client_tools.factory_jobcollection ref kind (with an optionalfactory), resolved incmd/codeaf/appcollections_factory.go.collections.dbschema 7: drops tables left by earlier builds of this branch and keeps every collection,membership and ref. app/v3 refuses a version-7 file (as it refuses 3-6) until this branch merges.
cmd/codeaftouched leg's timeout is 25 minutes (the base alone runs about 17.5); needs the CI owner'sagreement.
Known reds are the base's own (the same tests fail on app/v3's runs).
🤖 Generated with Claude Code