Skip to content

docs: bring mobile builds into the Objective's scope - #144

Open
lukemaj wants to merge 61 commits into
mainfrom
docs/objective-mobile-in-scope
Open

lukemaj wants to merge 61 commits into
mainfrom
docs/objective-mobile-in-scope

Conversation

@lukemaj

@lukemaj lukemaj commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

What: The Objective no longer excludes mobile builds.
Why: The user confirmed on 2026-10-02 that mobile builds are now in scope, so fork features like compact agent-message rows (#142) can reach their phone.
So what: Merging needs the user's approval; mobile follow-ups can then proceed without a direction detour.

Elon record

  • Requirements and who asked: the user said mobile builds came into scope.
  • Deleted: no new completion criterion was added; only the exclusion is removed.
  • Bottleneck: the exclusion blocks mobile work for feat(web): show agent messages as compact rows #142's rows.
  • Checked myself: read OBJECTIVE.md on origin/main; the only mobile mention is the exclusion line.

🤖 Generated with Claude Code

lukemaj and others added 30 commits September 27, 2026 15:38
Draft for user confirmation (2026-09-25): the fork becomes the user's single
working surface, stays a thin rebasable layer on upstream, runs Model Router
jobs as T3 threads and hosts OpenBot as a UI mode.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Confirmed by the user on 2026-09-25.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Name the stable desktop build Chromeria with its own bundle id
(md.toolbox.chromeria), Electron userData directory and icon set, so it
installs and runs beside upstream T3 Code. Threads stay in the shared
T3 home (~/.t3 or T3CODE_HOME).

Desktop builds no longer embed a GitHub update feed, so the updater can
never replace Chromeria with an upstream T3 Code release.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The #12 branding points production web favicons at assets/chromeria/, but
two brand-assets tests still expected the upstream production icons.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…MCP toolkit (#8) (#10)

* spike(server): threads MCP toolkit that spawns and messages child threads (#3)

Adds a threads toolkit (spawn_thread, message_thread, read_thread,
list_child_threads) next to device, preview and pullRequests. A bridge
mirrors each child thread's lifecycle into its parent's activities as the
task.* rows the Agents panel already folds, and can send each finished
turn back to the parent.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* spike(web): group child threads in a collapsible Subagents sidebar shelf (#3)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* spike(server): report steer for every provider and refuse messages to a starting child (#3)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* spike(web): hide child threads from the sidebar and open them from the Agents panel (#3)

Replaces the Subagents shelf: child threads are filtered out of the
sidebar, and their Agents panel rows link to the child thread.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(threads): production child threads with Agents panel listing and toolkit guards (#8)

Child threads spawned through the threads MCP toolkit are real T3
threads whose sub.<parent>.<suffix> id carries the parent link, hidden
from both sidebars and listed with status in the parent's Agents panel.

Production hardening over spike/3-threads-toolkit: documented the
id-convention tradeoff against a real parentThreadId (contract,
projector, persistence migration too invasive for this fork), extracted
the steer/new-turn delivery rule, pinned per-driver effort options, and
refused messages to still-starting children with a retryable error.

Built with OpenCode (Muse Spark).

* fix(desktop): run bundle self-check probe under plain Node (#8)

verifyPackagedBundleIsSelfContained spread ambient process.env into
the probe, so a shell with ELECTRON_RUN_AS_NODE=1 leaked the flag into
the validation command and broke the no-Electron-probe invariant the
Windows payload test asserts. Scrub the flag like the neighboring fff
probe curates its own env.

Built with OpenCode (Muse Spark).

* fix(tests): make failing gates hermetic without weakening assertions (#8)

- desktop: run the bundle self-check probe without ELECTRON_RUN_AS_NODE
  so validation does not depend on the orchestrator's ambient runtime flag.
- server: resolve the entrypoint fixture dir, matching the fully-resolved
  module URLs Node reports (macOS symlinks TMPDIR through /private/var).

Built with OpenCode (Muse Spark).

* fix(threads): consume the hot domain-event stream in the threads toolkit (#8)

The toolkit handlers layer builds with the HTTP routes. Acquiring
subscribeDomainEvents at build time failed every route-layer test whose
engine does not implement it (193 server.test.ts cases). Use
streamDomainEvents, as the upstream reactors do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* style: format GLOSSARY.md so vp fmt --check passes (#8)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The sidebar wordmark, setup screen, assistant author heading, link-target
and theme labels, splash screen and boot error still said "T3 Code".
They now read APP_BASE_NAME, whose browser fallback is "Chromeria", and the
T3 glyph renders the Chromeria obelisk icon.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs(fork): rebase routine, stack check and fork CI

Fork stays a small topic-commit stack on upstream main: docs/fork.md
describes the model, scripts/fork-rebase.sh absorbs upstream updates with
per-file conflict reports and proof-gated pushes, scripts/fork-check.sh
enforces the model, and .github/workflows/fork.yml runs it on every PR.
No upstream-owned files are modified.

Refs #6

* fix(fork): fork-rebase listing must survive head closing the pipe

* test(fork): focused tests for the rebase routine safety rules

fork-check.sh and fork-rebase.sh are driven against throwaway git
fixtures: new-file pass, unallowlisted-edit fail, allowlist pass,
merge-commit fail, missing-doc fail, no-proof push refusal and
no-main push refusal.

Refs #6

* docs(fork): list maintenance tests and fix formatter whitespace

Refs #6

* fix(fork): run proof in a subshell cleared of ELECTRON_RUN_AS_NODE

* docs(fork): Chromeria install, squash-plus-absorption model and GitHub-hosted CI

Document building, installing and updating the Chromeria desktop app,
land absorptions on main with --force-with-lease after proof, allowlist
every fork edit of upstream files, remap the PR and main CI workflows
from Blacksmith to GitHub-hosted runners, and run the routine's proof in
a host-neutral environment.

Refs #6.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* ci(fork): check the PR head, not GitHub's merge ref

Refs #6.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(fork): allowlist the #14 web branding edits

Refs #6.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): remove unused threads toolkit export

* fix(web): provide branding in bundled dev fixture

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
) (#18)

* feat(web): Prism spawns, collapsed child tree and parent breadcrumb (#17)

The Agents panel now separates threads Model Router (Prism) started for
this thread from the thread's own spawns, grouped by job with status and
route. Router threads are recognized from the [model-router job ...] tag
in their first message, read once per thread and cached. Rows with child
threads show a collapsed chevron with the child count that reveals
compact nested rows. A child thread's header shows the parent title as a
link plus a menu to switch between siblings.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): keep Agents tree internals unexported for knip (#17)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ovider snapshot (#22)

* feat(contracts): Prism role kits and provider snapshot schema (#19)

Role kits (instructions, runtime mode, skills, thread-tool scope) and
ordered model preferences for the six Prism roles, stored as the
project-scoped server setting prismRoles, with a per-role patch. Adds the
GET /api/prism/snapshot response schema for the router.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(server): Prism MCP toolkit, spawn_thread(role) and provider snapshot endpoint (#19)

Adds prism_submit, prism_status, prism_questions and prism_answer, which
run the installed Model Router CLI with the calling thread as the T3
planner and a server URL plus an orchestration-scoped token supplied by
the server. spawn_thread takes a Prism role, applies its kit and first
eligible preferred model, and each role's thread-tool scope is enforced.
GET /api/prism/snapshot returns providers, models, usage windows with
resetsAt and the role kits resolved for a project. OpenCode usage now
refreshes on the provider health interval.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore: drop unused Prism exports flagged by knip (#19)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(contracts): Prism model lists per role and lane (#19)

Each role keeps an ordered list of {instanceId, model, effort} per lane
(easy, medium, hard): the first entry is the primary choice, the rest are
fallbacks, and one model at another effort is its own entry. Adds
PrismLane (default medium) and per-lane patching. The snapshot carries
the same structure through PrismRoleKits.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(server): pick role models by lane in spawn_thread and prism_submit (#19)

spawn_thread takes an optional lane (default medium) and walks that
role's lane list, primary first then fallbacks. prism_submit takes
easy, medium or hard and maps them onto the installed router's
small, default and hard lanes. PRISM_LANES stays module-local for knip;
PrismLane.literals lists the lanes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* wip: add Prism role preferences and capacity settings (#21)

* wip: edit easy medium and hard lanes for every Prism role

* wip: derive Prism lanes from the exported schema

* wip: serialize Prism saves through the settings snapshot

* wip: retain Prism save guard across scope navigation
* feat(fork): map fork features and report upstream overlap

* fix(fork): register report entry point and validate feature ids

* docs(fork): point edit guidance to the feature map
…ker (#26) (#35)

* feat(contracts): single model lists outside Worker, Retry and Escalation switchable (#26)

Only the worker keeps easy/medium/hard lists; every other role has one
ordered list under models, and a saved per-lane list keeps its medium
entry as that list. Retry (correction) and Escalation (recovery) gain
enabled. The snapshot still repeats each single list in every lane until
toolboxmd/model-router#127 reads single lists. spawn_thread accepts retry
and escalation next to the old names.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(web): Prism settings in the Providers layout with autosave (#26)

A role list (icon, name, description, toggle) and a detail pane with one
ordered model list per role, reorderable by drag or arrows and saved on
every change. Only Worker shows easy/medium/hard tabs. Retry and
Escalation can be switched off; Dispatcher, Worker and Reviewer show
locked on with the reason. Planner is no longer on the page. The Agents
panel shows each Prism job as a heading without a status dot.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): show locked Prism roles with a lock and keep model rows on one line (#26)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(web): drop unused applyPrismRoleUpdate export flagged by knip (#26)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Upstream main is now 113 commits past the fork base, so the
`--depth 100` fetch no longer contains the merge base and
fork-check.sh exited 1 silently under set -e. Fetch upstream main with
full history but no blobs, and print a clear error when the merge base
cannot be found.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d status (#25) (#37)

* feat(web): show parent threads as working while child threads work (#31) (#33)

* feat(web): show parent threads as working while child threads work (#31)

Child threads are hidden from the sidebar, so their work never reached the
parent row. A shared pure helper derives descendant child-thread activity
from thread shells; the sidebar row feeds it through the existing status
resolvers (Working pill and timer with "· N agents", child approvals as
the parent's approval), and the auto-settle sweep keeps such parents
active like threads with live background work.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(fork): record PR for sidebar-child-working (#31)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): surface child input, time from the earliest child (#31)

Review fixes: a child waiting on user input now shows the parent's
awaiting-input state, and children waiting on input or with any background
work (monitoring included) keep the parent from auto-settling. An idle
parent's Working timer starts at its earliest working child, and the
"· N agents" count sits outside the aria-hidden timer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(web): browse GitHub Issues next to PRs (#27) (#32)

* feat(web): browse GitHub Issues next to PRs (#27)

The Pull Requests page gains a PRs/Issues toggle (`view=issues`) and the
sidebar an Issues icon. Issues from every GitHub repository of the user's
projects are read live through `gh` (one GraphQL search per host page,
within the existing GraphQL budget and rate-limit handling), merged across
connected environments, and shown as Open, Done or Not planned. Search,
sort, repository/label/milestone/parent filters, a parent tree that marks
sub-Issues outside the user's projects, and a side panel with body,
comments, comment, close and reopen. The command palette searches the
listed Issues while the page is open.

No storage: upstream tables, migrations and events are unchanged. Upstream
edits are allowlisted and mapped under the `issues-browse` feature.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(fork): map the Issues browse PR (#27)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): surface Issues list failures and address review (#27)

- Show a failed server or "Load more" page with Retry above the rows that
  stay, and stop offering "Load more" while it failed.
- Toasts for comment and close/reopen carry GitHub's reason.
- Locked Issues keep the comment box (GitHub has no viewerCanComment); a
  note explains and GitHub's refusal is shown.
- Count non-GitHub repositories once by host and repository across
  worktrees, pages and servers.
- Palette Issues group follows Actions and appears only once typed.
- Remove unused scoreIssueMatch and knip-flagged exports; table tests for
  issueStateOf and issueSearchQuery; typecheck fix in the live suite.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(server): link GitHub Issues to threads (#28) (#34)

* feat(server): link GitHub Issues to threads (#28)

Store only non-derivable Issue links in the fork-owned
fork_thread_issue_links table (created by the fork, outside upstream
migrations and the event log). Derive branch links from conventional
<type>/<number>-<slug> branches and closing-reference links from the
thread's linked pull requests (real gh) or, for Issues, from the closing
pull requests the caller already read.

Expose issueLinks.* RPCs (for thread, batched threads for Issues, link,
unlink, change push) behind an issueLinks capability,
link_issue/unlink_issue/list_thread_issues MCP tools, an Issues section
in the "Linked PRs and Issues" panel, and a start-thread-from-Issue
action that opens a draft in the matching project, prefills it and links
it at once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): keep retried threads' Issue links and refresh on batched changes

- Prune deleted threads' links only once deleted over a week ago: a failed
  first-send bootstrap deletes a thread and the retry re-creates its id.
- Deliver change pushes to the client one batch at a time and match a
  batch if any change names the thread or Issue.
- Render the Issues section only where the server advertises issueLinks.
- Enable and rename the command palette entry for the linked surface.
- Cover caller-supplied closing references in CI with real rows.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(web): computed Issue status with grouping and filters (#29) (#36)

* feat(web): computed Issue status with grouping and filters (#29)

Each Issue gets one status from the #25 table, first match wins: GitHub
state and close reason, open native blockers, closing pull requests
(open, draft) with the trusted `review/independent` mark on their head,
task-branch links, and whether a linked thread or any descendant child
thread works now.

The Issues page read gains blockers, closing pull requests with head SHA
and the review mark in the same per-page GraphQL query (cost 2 -> 3 per
100 rows); marks count only when the query's viewer posted them. The
view groups by status with Done and Not planned collapsed, adds Status
and Linked filters, shows linked threads on rows, wires Start thread on
rows and the side panel, and opens the side panel from a thread's linked
Issue through the `issue` search parameter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): count thread-linked PRs, trust marks across servers, drafts pause (#29)

Review fixes for PR #36:

- A PR into a non-default branch makes no closing reference, so the
  Issue's PRs are now its closing PRs plus the open PRs linked to its
  linked threads, deduped by repository and number. Each host's first
  search page reads those thread-linked PRs as `resource(url:)` aliases in
  the same GraphQL request (cost stays 3 per 100 rows with 50 aliases; a
  missing PR answers null instead of failing the page), and
  `threadsForIssues` returns each thread's PR keys.
- Threads are looked up on every server that keeps Issue links, and a
  review mark is trusted when any server that listed the Issue runs as
  its poster; the server now returns the mark's creator and its viewer.
- An open draft PR with nobody working reads as Paused; merged PRs keep
  not counting while the Issue is open.
- The status table moves to contracts so the live suite checks it too.
- The Issues view subscribes to a working-thread key atom instead of all
  thread shells, and a row's Start thread waits and ignores repeat clicks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): read only PRs of Issue-linked threads, also without a search (#29)

Re-review fixes for PR #36:

- Thread-linked pull request candidates now come from threads that link
  to an Issue (a stored, non-dismissed link or a task branch), through a
  new `IssueLinks.pullRequestsOfIssueThreads`. Last-synced snapshot state
  no longer filters them: the same GraphQL read returns GitHub's state.
  The 50 cap stays as a bound and logs when it truncates.
- A server whose repositories a filter excludes still reads its
  candidates and viewer in a small aliases-only request.
- While one row starts a thread, every row's Start button is disabled.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): close Issues integration gaps (#25) (#38)

* fix(web): close Issues integration gaps (#25)

- Servers advertise an `issues` capability; the Issues list, the sidebar
  Issues icon, the palette's "Open issues" action and the PRs/Issues
  switch use only servers that have it, so upstream and older servers no
  longer answer `issues.list` with an unknown-tag error.
- The side panel shows the Issue's linked threads (open one) and its
  closing and thread-linked pull requests (open on GitHub), read for the
  one Issue, so it works when opened from a thread's link too. The Issue
  detail read now carries closing pull requests.
- The command palette finds threads by linked Issue: `#N`,
  `owner/repo#N` or an Issue URL asks `threadsForIssues` on servers with
  Issue links, debounced and bounded to 20 repositories per server.
- Start thread prefers a project on a server that keeps Issue links.
- User docs cover linking by hand, unlinking and the MCP tools.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): find closing-reference threads from the palette (#25)

Review fixes for PR #38:

- The palette's Issue lookup sends the Issue's closing pull requests:
  from the loaded Issues list entry, else, for `owner/repo#N` or a URL,
  from one debounced Issue read on a server that lists Issues. A bare
  `#N` the list does not hold finds stored and branch links only; the
  user docs say so.
- An `issue=` link without a usable `selectedEnvironmentId` resolves to
  a server that lists Issues, one keeping Issue links first, and never
  to a server without `issues.*`.
- The detail read's mapping moves to `issueDetailOf`, with a CI test
  decoding a fixture with one closing pull request and one null node.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(web): open PRs from Issues and chat links in the app (#40)

The Issue side panel's pull requests opened GitHub, and GitHub `/issues/N`
links in chat (including `#N` autolinks, which GitHub numbers together with
pull requests) opened GitHub or nothing. Pull request links inside an Issue
body updated the PR panel while the Issues view stayed in front.

- Issue panel PR rows open through the existing change request opener, with
  a separate "Open on GitHub" button.
- A new on-click resolver reads N as a pull request, then as an Issue, and
  opens the PR panel, the Issues side panel, or the browser. Nothing is read
  on render; every failed read falls through to the browser.
- The PR opener leaves the Issues view when it navigates the PR page.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): address review of Issue and PR link opening (#40)

- On the web, when the click's user activation lapsed during the reads, the
  browser fallback shows a toast with an "Open on GitHub" action instead of
  a window.open the popup blocker would swallow.
- Leaving the Issues view to open a pull request pushes a history entry, so
  Back returns to the Issue.
- A repeat click while a link is being read joins that read.
- The user guide says the fallback follows the "Open links in" setting.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): ask for a fresh click only when the fallback opens a web tab (#40)

The "Open on GitHub" toast now appears only when the fallback would open a
new browser tab on the web after the click's activation lapsed, not when
"Open links in" sends the link to the in-app browser or on desktop.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(web): Issues filters use the PR Filters menu (#42)

The Issues toolbar had its own State dropdown, a sort button labelled with
the current sort and one long Filters list. It now uses the pull request
list's Sort button, Filters trigger and per-filter submenus. Project, Labels
and Parent search like the Author filter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): keep active Issue filters visible and test filter search (#42)

Milestone and Parent keep an active value listed after the loaded rows stop
carrying it. The Filters count includes State like PR mode, and Clear filters
resets State too. The searched submenu option logic moves into a pure helper
with unit tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
)

* fix(web): PR panel no longer shows "All fibers interrupted" on open (#52)

Opening a pull request starts its detail read twice within a few ms. The
server lets the second read join the first read's shared lookup while the
first read's cancellation is tearing it down, and answers it with an
interrupt. The client query atom stored that as a failure, so the panel
showed "All fibers interrupted without error" until Retry.

An atom never observes its own cancellation, so an interrupt-only failure
in an environment query is the server's. The query now reads once more
instead of surfacing it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(fork): link the query-interrupt-retry feature to PR #54

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tart (#48) (#49)

* fix(server): child threads keep reporting to their parent after a restart (#48)

The threads toolkit bridge kept each child's reportBack flag and the last
reported message only in memory, so after a server restart no child turn
was reported back to its parent. Write both into the parent's task.* rows
and restore them from there the first time a child's session changes after
a restart.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): restore child report state without trusting the activity window (#48)

Review of #49: the parent's thread detail holds only its newest 500
activities, so a child's task.started row and last reportedMessageId row
can fall out of it. Every idle task.progress row now carries reportBack and
the latest reportedMessageId, and restore reads the newest row. A child with
no surviving row treats its current last reply as already reported. One
parent read restores all of that parent's children. The restart test now
covers a reportBack:false child.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Harnesses that defer tool schemas show agents only tool names, and Grok
not even those, so planners missed prism_submit and spawn_thread and
opened terminal sessions instead. Every harness's launch prompt now
carries a short t3-code tool guide: prism_submit for an authorized job
that ends in one PR, spawn_thread for small work or a named model and
effort, plus the browser, device and Issue tools.

The text lives in apps/server/src/mcp/toolInstructions.ts. The launch
prompt stays its home: MCP server instructions would reach Claude Code,
Codex, OpenCode and Grok, but a live ACP run showed Cursor does not show
them to the model, and Antigravity only writes them to a file.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fork-check.sh failed any PR once the fork stack exceeded 20 commits,
an arbitrary limit from PR #9 with no evidence behind it. Main is
already at 20 commits, so every fork PR fails the check now.
Rebase cost comes from upstream-file edits, which the allowlist and
feature map already enforce; the count adds nothing.

Drop the --max-commits option and the failure branch, keep the
informational "stack: N commit(s)" line and commit list, and update
docs/fork.md accordingly. No test asserted the cap.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…el (#56)

* feat(server): Prism stream clock for worker liveness, plus prism_cancel

Model Router measured worker silence from ~100-char message segments with a
fixed window, so a dead turn went unnoticed for minutes and a slow but
healthy model risked being killed. Prism also had no way to cancel a job.

Every provider event already passes through the shared native and canonical
event loggers, including each token, reasoning and tool output delta and
Claude thinking_tokens. A new in-memory StreamClock wraps those loggers and
stamps each thread's lastStreamAt, tracks open tool calls, and at each turn
end records one hidden prism.stream-stats activity with time to first token,
max gap while no tool was open, event count, provider, model and outcome.
GET /api/prism/liveness?threadId= returns it with the server's now.

prism_cancel checks the job belongs to the calling thread, then runs the
Router CLI cancel and recover with a Prism token and returns the final state.

Closes #55

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(fork): map the Prism stream clock feature to its PR (#55)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): build the tapped event loggers on the upstream layer; drop unused exports (#55)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(server): child threads no longer flood the mobile thread list (#59)

The store mobile app has no Agents panel, so every child thread spawned
through spawn_thread or Prism showed up in its thread list and buried the
threads the user started.

The server now leaves child threads (sub.<parent>.<suffix>) out of the
shell it sends to mobile connections: the WebSocket shell snapshot, replayed
and live shell events, the archived shell snapshot, and the HTTP shell
snapshot, which looks up the surface the session last connected with. Web
and desktop still get child threads for the Agents panel. No mobile build
is needed; a phone drops cached children on its next connection because new
sessions reload the HTTP snapshot.

Until upstream Orchestrator V2 ships, child threads cannot be opened on
mobile; they stay reachable from web and desktop.

Done by Claude Opus 5.5 in Claude Code.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(fork): link #60 to the child-threads feature

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…63)

* feat(server): threads resume on their own after a usage limit resets (#61)

When a provider's usage limit stopped a thread, nothing brought it back:
the user had to return after the reset and type "continue". Three
threads stopped this way on 2026-09-28 alone.

When a thread the user started fails while its provider instance has an
exhausted usage window with a known reset (the data the Usage panel
shows), the threads toolkit sends one "Continue. (Sent automatically after
the usage limit reset.)" a minute after the latest reset. Starting another
turn or archiving the thread first cancels it. Child threads are skipped:
Model Router reroutes or blocks Prism jobs itself, and the planner decides
what to do with a blocked job once it resumes. Pending resumes live in
memory, so a server restart drops them. Upstream Orchestrator V2 has its
own "Resume at reset", so this goes away with the V2 port.

Done by Claude Opus 5.5 in Claude Code.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(fork): link #63 to the child-threads feature

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): keep the usage-limit settle delay private

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): keep mobile shell helpers private (#60 follow-up)

Knip flagged three exports only used inside mobileShell.ts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(server): detect stale turns from measured stream gaps

* feat(server): monitor stale children and notify their parent once

* test(server): encode saved stream samples through the schema

* fix(server): keep notifyStaleParent module-private (#62)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…LIs they replace (#67)

* feat(server): delegation tools are always loaded and name the shell CLIs they replace (#66)

spawn_thread, read_thread, message_thread and prism_submit carry
_meta["anthropic/alwaysLoad"], so Claude Code shows their descriptions
from the first turn instead of deferring them behind tool search. The
spawn_thread and prism_submit descriptions and the launch paragraph now
say when to use them and name codex exec, claude -p, opencode run and
grok in the shell as what they replace.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): spawn_thread description says a role applies its kit (#66)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ir GitHub state (#70)

* fix: prism_submit names its workspace default; linked Issues show their GitHub state

- prism_submit's description says workspace defaults to this thread's
  current directory and must be set for another repository or worktree
  (#68). AGENTS.md notes running vp as `pnpm exec vp` where it is not on
  PATH.
- The Linked PRs and Issues panel read no state for Issues and drew every
  row with the open icon; the footer counted only pull requests. The panel
  now reads each linked Issue's current state, rereads it on each pull
  request sync, draws GitHub's state glyph, and counts open Issues in the
  footer (#69).

Closes #68
Closes #69

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: linked Issues read state alone, refresh on an interval, show unknown

Review of d6434a1 (Luna):
- Failed or missing reads are their own `unknown` state: shown with a
  question glyph and not counted open. Before the first read a row is
  `pending` and is not counted either.
- The panel's states query refreshes every 60s while shown, besides mount
  and each pull request sync.
- New `issues.states` RPC reads only `state stateReason` for all linked
  Issues in one aliased GraphQL request per host, instead of one full
  `issues.detail` read (body, comments, closing references) per Issue.

Tests: IssueService.states with a fake gh (request shape, null for a
missing Issue, failure on a gh error, no request for no Issues); the
query runtime rereading on its refresh interval; the panel's state
transitions pending -> open -> done, and failed or missing -> unknown.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: read every linked Issue's state in chunks of 50; render-test the panel

Re-review of 4435f31 (Luna):
- The panel no longer slices linked Issues to 50. It reads them in chunks
  of at most ISSUE_STATES_MAX, one states query per chunk, and a failed
  chunk leaves only its own Issues unknown.
- ThreadLinksPanel.test.tsx renders the panel for pending, failed,
  missing, open, done and not-planned states and checks each row's icon
  and the footer's open and linked counts, including an Issue closed
  after linking.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…Chromeria (#77)

The sidebar sized the square Chromeria icon at 1cap (about 10px), a height
meant for the old wide T3 wordmark. It is now 20px and centered with the
name; the welcome screen mark gets the same fix. t3.json points the
project icon at the Chromeria mark instead of the T3 blueprint.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s Mac (#78)

* docs: AGENTS.md lets agents stage the merged build on the maintainer's Mac

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(ci): allowlist fork edits to AGENTS.md and the PR panel; drop an unused export

Main's Fork Stack Model and knip checks failed after #70 and #76.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
lukemaj and others added 26 commits September 30, 2026 07:47
…a fix (#86) (#87)

The launch paragraph and prism_status now say: have a reviewer child
thread confirm a Prism defect from prism_status and the durable-runner
outputs, then open or update the model-router Issue with that
confirmation, then start a worker child on the fix in its own worktree,
while the job keeps going. Unconfirmed suspicions become a comment on an
existing Issue or are dropped. Tests pin the steps and their order.

Closes #86

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…untimeMode (#89)

* fix(server): spawn_thread no longer lets agents set runtimeMode

An agent set approval-required on a background child on its own, so
every approval went to the user and the child sat blocked while the
parent reported it running. The child's mode now comes only from the
role kit, else the parent thread. A runtimeMode passed anyway is dropped.

Closes #88

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): children start in full access; drop the role-kit runtimeMode

A child's approvals go to the user, never to the planner, and the planner
cannot see or answer them, so any restricted mode stalls a child unseen.
Copying the parent's mode was also wrong: a read-only dispatcher would
make its implementers read-only. Children now always start in T3's default
full-access mode, and the unused role-kit runtimeMode is removed. Saved
settings that still carry it load and drop the key.

Refs #88

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Upstream: pingdotgg/t3code#13970 by @asonawalla, plus fork-map registration and a khroma@2.1.0 MIT license override.

Co-authored-by: Azim Sonawalla <azim.sonawalla@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mission: Prism chooses model, harness and effort per delegated role and runs
every agent as a visible T3 thread. Objective: dispatcher on spawn_thread,
one set of role names with every thread tool, runner retired.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…#100)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ames (#93) (#101)

* feat(server): every Prism role has every thread tool; roles use six names (#93)

Delete the per-role thread-tool levels (threadToolRefusal, the default
scope table and the threadTools kit field) so any thread in any role may
call spawn_thread, message_thread, read_thread, list_child_threads and
list_threads. Rename the internal role keys correction/recovery to
retry/escalation. Settings saved with the old keys or a threadTools field
still load, and child ids spawned as correction-/recovery- read as the new
roles. The runner's snapshot route keeps its legacy keys until it retires.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: record #101 in the Prism feature entry

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
)

* feat(server): interrupt_thread stops a thread's running turn (#95)

New thread tool: interrupt_thread(threadId, scope) dispatches
thread.turn.interrupt for a running or starting thread and returns once
T3 reports the session settled (interrupted), or interrupt_requested if it
has not settled within 30 seconds. An idle, failed or stopped thread
returns no_active_run without a command. Result statuses mirror upstream
orchestration V2's t3_thread_interrupt so callers port unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: record #102 in the child threads feature entry

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): a stale interrupt never stops a newer turn (#95)

The provider reactor now drops thread.turn-interrupt-requested when it
names a turn that is no longer the session's active turn, so an interrupt
sampled for turn A cannot stop turn B that started before the request was
handled. interrupt_thread treats the sampled turn ending as settled, even
when a newer turn is already running.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): interrupt_thread never sends an unscoped interrupt (#95)

A starting thread has no active turn id yet, and an interrupt without one
could stop a newer turn that becomes active before the reactor handles it.
interrupt_thread now waits up to 30 seconds for the thread to report its
turn id and interrupts that turn only; if the thread stops being active or
reports none in time, it returns no_active_run and sends nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tart (#94) (#103)

* fix(server): report-back reaches the parent exactly once across a restart (#94)

Restore every child's report state from all parent task rows, read by
kind, instead of the parent's newest-500 activity window, so a child keeps
its reportBack setting however many parent activities followed. Send each
report with a command id derived from the reply, so the engine's command
receipts drop a resend, and send it before writing the idle row. On start,
report idle children whose latest turn completed after their last recorded
idle row, which no bridge saw while the previous process stopped.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: record #103 in the child threads feature entry

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): no idle transition is lost while report-back catches up (#94)

The bridge now subscribes to domain events before its restart catch-up
and drains the buffer afterwards, so an idle transition that lands during
the catch-up is handled by the bridge instead of falling between the two.
The catch-up reads only children whose newest parent row is not idle and
writes their idle row, replacing the shell-snapshot scan (which also added
a shell snapshot read that server.test.ts counts). Duplicates are dropped
by lastReported and the report's command receipt.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(server): route delegated work through spawn_thread roles (#96)

The tool-instruction block now tells every coordinating thread to delegate
with spawn_thread roles, start a dispatcher for one-PR jobs, and recover a
failed child with interrupt_thread, retry, then escalation. prism_submit stays
only as the fallback until #97 removes it. spawn_thread and interrupt_thread
descriptions carry the mechanics; a test pins that Prism role kits ship no
default instructions.

Refs #96

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): replace a child only after its interrupt settles (#96)

Review on #104: interrupt_requested can leave the child running, so the
replacement waits for a settled report; spawn_thread notes reportBack: false.

Refs #96

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…limit (#72)

* feat(server): direct child threads resume on their own after a usage limit (#71)

A spawn_thread child that stopped on a usage limit waited until the user
typed "continue" to the planner and the planner relayed it: #63 skipped
every sub.* thread to leave Prism jobs to Model Router.

The resume watcher now skips only Prism job threads, recognized by Model
Router's "[model-router job ...]" first user message (titles get
regenerated, so they are no marker). A resume fires at the earlier of the
displayed reset plus a minute, or the first reply any thread gets from the
same provider instance after the settle delay: on 2026-09-29 the displayed
reset was 2.5 hours later than when requests worked again. A thread whose
early resume fails again waits for the reset, so a per-model limit cannot
turn replies elsewhere into a retry loop. A resumed child's parent gets
one line saying so.

Done by Claude Opus 5.5 in Claude Code.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): interrupt_thread cancels a usage-limit auto-continue (#71)

A dispatcher interrupts a limit-hit child before replacing it, but that
child is already failed, so interrupt_thread had nothing to stop while the
pending automatic continue would still restart it next to its replacement.
interrupt_thread now cancels that pending continue and marks the stopped
turn so a later limit error for it schedules none.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): continue only usage-limit errors, and past resets keep watching (#71)

Only a turn whose own error reads as a usage limit schedules a continue;
an unrelated runtime error while a usage window is exhausted is left
alone. An exhausted window whose reset time already passed no longer ends
the watch: it keeps waiting for a reply on the instance and re-reads the
windows every 5 minutes, resuming at a future reset or once none is
exhausted, so a stale reading cannot turn into a continue loop.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): resume at a reset only once the window shows it lifted (#71)

At a future reset plus the margin the watcher resumed without reading the
usage windows again, so a window still at 100 % got a continue anyway. It
now re-reads them and resumes only when none is exhausted; otherwise it
keeps re-reading every 5 minutes, and a reply on the instance can still
resume it early.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Python runner is retired, so its Chromeria hooks go: the five prism_*
MCP tools, GET /api/prism/snapshot and /api/prism/liveness with their
contracts, the stale monitor's title exception, the usage-limit resume's
job-thread exception, the prism_submit fallback and router-defect guidance
in the tool instructions, and the Agents panel's Prism spawns section.
Stall detection, parent notification, per-turn stream statistics, Prism
roles and Settings stay.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs: glossary names the Promachos, Satrapies and Libraries; Prism is the router

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: glossary adds Wight mode

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: glossary adds Color and Spectrum

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: a Spectrum holds Drafters of several Colors

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(web): open thread issue links in the right panel

* fix(web): linked Issue buttons name the right panel

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): keep the Issues page opener module-local

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(server): spawn threads into another project

* fix(server): keep spawn tool description short
* feat(prism): select the initial Promachos conversation model

* style(web): wrap Prism page roles array for the formatter

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(server): drop the absolute epoch from Promachos RPC selection cases

The shared TestClock advances in earlier cases, so the Promachos RPC cases
saw 1970-01-03 instead of the epoch. The case still checks that the created
thread and the turn share one timestamp and the selected model.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(server): retire stopped thread subtrees durably

* fix(web): stopped child threads show as stopped in the Agents panel

A retired thread's session is interrupted but its last turn can stay
running or error, so the panel showed it as running or failed. A live or
interrupted session now outranks the last turn; a restarted session still
shows running.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): keep retirement turn classifier private

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(web): Promachos mode chats with the home's conversations in bubbles (#116)

A switch in the sidebar header moves web and desktop between the standard
view and Promachos mode. Promachos mode lists the conversations of the
Promachos home (one project on one environment, chosen per client) and
starts new ones there through one function, useStartPromachosConversation,
which #115 later points at the Promachos's Prism role.

Home threads render as a chat: the user's messages as outbound bubbles,
replies as one bubble per top-level Markdown block within a single parse,
so code, lists and tables stay whole and reference links resolve. Tool
activity, reasoning and turn folds collapse into a static working bubble.
The composer's own question and approval cards render inline in the chat
through a portal, keeping their handlers and state.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): the Promachos mode switch gets its own sidebar row (#116)

At the default sidebar width the switch beside the brand overflowed the
titlebar into the main header. It now sits on a full-width row under the
titlebar, with text labels only, so it fits at the minimum width beside
any window-controls inset. The titlebar itself is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: record Promachos mode PR in the fork feature map (#116)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): Promachos cards stay with the thread whose timeline shows them (#116)

During a thread switch the timeline can keep painting the previous
thread while the composer already belongs to the next one, so that
composer's question and approval cards rendered into the previous
conversation. The inline card host now records the thread whose timeline
rendered it, and the composer only uses a host its own thread owns;
otherwise the cards stay attached to the composer.

Also drops the unused isPromachosHome export that knip reported.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(web): new Promachos conversations start on his Prism role (#116, #115)

A first send that creates a top-level thread in the Promachos home, in
Promachos mode, now carries prismRole "promachos" on thread.turn.start when
the home's Promachos model list has entries. The server picks the model and
replaces the composer's selection, which stays as a valid placeholder. With
an empty list the conversation starts normally. A Prism refusal is the
send's own failure and shows in the chat; it is never retried as a normal
start. The seam is withPromachosStart, one wrapper on the existing send.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): no multiple-model start where Prism picks the Promachos model (#116, #115)

The multiple-model start creates top-level threads straight through
startThreadTurn, so in a Promachos chat with a configured list it bypassed
the Prism start. The composer now offers no multiple-model start there and
sends no stale fan-out selection, so every new conversation goes through
withPromachosStart. With an empty list nothing changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): a hidden multiple-model selection no longer locks Promachos workspace controls (#116)

Where Prism picks the Promachos model, ChatView now acts on one effective
multiple-model selection, none, for the composer, the workspace mode
switch and the branch toolbar's forced new worktree. The saved selection
stays in its atom, so it returns when the view goes back to Code.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(server): continue idle threads in Wight mode

* docs: register Wight mode fork ownership

* fix(server): recheck Wight eligibility at turn admission

* fix(server): guard legacy resumes and share turn sender

* docs: keep shared orchestration ledger ownership singular

* fix(server): keep Wight continuations behind retirement guards
* feat(spectrum): add council and free discussions

* fix(server): preserve Spectrum recovery on supported Node
* feat(server): record thread ownership and sharing

Co-Authored-By: GPT-6.1-Sol <noreply@openai.com>

* fix(server): carry thread sharing through detail streams

Co-Authored-By: GPT-6.1-Sol <noreply@openai.com>

* feat(web): person picker and thread sharing

Devices are labelled with a person in Settings -> Connections. The standard
and Promachos sidebars get a Luke / Pauli / Shared / All picker that filters
threads by owner and co-owners; unsent drafts count as the device's person.
Shared threads carry a Shared label in the sidebar and the thread header,
where the owner can share or unshare and a co-owner can leave. Sharing
activity renders as its own timeline row, and thread.sharing-set events
update thread detail state live. A device refetches its own label when its
window regains focus.

Refs #121

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): stamp people on HTTP dispatch

Co-Authored-By: GPT-6.1-Sol <noreply@openai.com>

* fix(server): Spectrum threads inherit the caller's owner

Co-Authored-By: GPT-6.1-Sol <noreply@openai.com>

* docs: link the people feature to its PR

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): create people columns at runtime, not as migration 55

Co-Authored-By: GPT-6.1-Sol <noreply@openai.com>

---------

Co-authored-by: GPT-6.1-Sol <noreply@openai.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(server): add check-gated scheduled tasks

* feat(web): manage scheduled tasks in Settings

Settings → Scheduled tasks lists each task's health (last run, pinned check
version and verdict, failure streak, last error, chosen minute and
neighbours) and manages tasks: create with a required check and reason,
edit, pause and resume, run now, delete, and revert check versions with a
reason. A finished turn with a failing check is shown as continuing, never
as done. Adds a user guide.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): deliver scheduled Spectrum reports once

* chore: register scheduler projector as shared with people

* fix(server): preserve scheduled run ownership and recovery

* fix(web): scheduled tasks follow the selected environment and needs-you rules

The Scheduled tasks page now requires one selected environment and asks for
one otherwise, instead of listing a stand-in environment's tasks. Search uses
the environment scope. Only a verified run counts as settled: on a task that
needs you, Run now resumes the same run and Delete is left to the server's
quiescence check, while work in progress blocks both. Guide updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: link scheduler delivery pull request

* fix(server): register scheduler tools and preserve run verdicts

* test(server): provide scheduler subscriptions in route fixtures

* fix(server): keep scheduler idle helper private

* chore(web): keep scheduled task helpers private to their module

Knip reported five exports used only inside ScheduledTasksSettings.logic.ts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Server-origin turn commands omitted modelSelection, so the provider
reactor sent turns without it and Codex fell back to medium reasoning
effort. The shared fork turn sender (threads toolkit, Wight, usage-limit
resume, scheduler), the Spectrum turn builder (Colors, moderator, caller
report) and the Prism stale-child notice now carry the target thread's
stored modelSelection.

Closes #133

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Git appends the partial-clone filter to fetch lines in `git remote -v`
(`origin	<url> (fetch) [blob:none]`). The three remote-line parsers were
anchored after `(fetch)`/`(push)`, so partial clones lost repository
identity, listRemotes dropped the remote, and ensureRemote added a
duplicate. Accept an optional trailing bracketed filter in all three.

Closes #135

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…quest (#137)

Restart recovery read a Drafter's latest request and then dispatched a plain
session stop, so a genuine successor request landing in between was stopped.

The stop now names the request it may end by its event sequence. The engine
refuses it, inside the serialized decide step, when the Drafter has a newer
request, and always refuses the legacy unnamed form. A refused stop is never
persisted, so neither the reactor nor any client projects it as stopped.
Spectrum's outbox moves on only when that refusal is a superseded stop, live
or replayed. Accepted stops precede every later request, which the reactor
handles only after the physical stop finishes.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XS labels Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.5 KiB 13.5 KiB +1 B (+0.0%) 15.1 KiB ✅
Codex Thread snapshot wire 7.1 KiB 7.1 KiB +8 B (+0.1%) 7.3 KiB ✅
Codex Live turn WebSocket wire 6.4 KiB 6.4 KiB −7 B (−0.1%) 7.8 KiB ✅
Codex Live turn WebSocket decoded 56.2 KiB 56.2 KiB 0 B (0.0%) 66.4 KiB ✅
Codex Live turn messages 9 9 0 (0.0%) 21 ✅
Claude Total thread wire 13.5 KiB 13.5 KiB +3 B (+0.0%) 15.1 KiB ✅
Claude Thread snapshot wire 7.1 KiB 7.1 KiB −4 B (−0.1%) 7.3 KiB ✅
Claude Live turn WebSocket wire 6.4 KiB 6.4 KiB +7 B (+0.1%) 7.8 KiB ✅
Claude Live turn WebSocket decoded 57.0 KiB 57.0 KiB 0 B (0.0%) 66.4 KiB ✅
Claude Live turn messages 9 9 0 (0.0%) 21 ✅

Baseline: 7dd561e · PR result: 0dccad9 · Source CI: failure

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 114.0 KiB
  • Claude decoded thread snapshot: 114.7 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

This branch has not been deployed

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

Labels

size:XS vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant