Skip to content

feat: workstreams W4 — the conductor reads across workstreams without a closed one's text, and acts through workers homed where the work belongs - #465

Merged
saucam merged 4 commits into
feat/workstreams-w3from
feat/workstreams-w4
Nov 12, 2026
Merged

saucam merged 4 commits into
feat/workstreams-w3from
feat/workstreams-w4

Conversation

@saucam

@saucam saucam commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #464 (W3) → #463 → #462 → #461. Design: docs/workstreams-design.md §10, plus §12.2 "W4 as built". User guide: docs/CONFIGURATION.md, under "The conductor and workstreams".

What W4 enforces is on only with workstreams.isolateGlobalServers.

What

  • Reading:
    • Fleet tools name each result's workstream, and fleet_list, fleet_find and fleet_recall take a workstream filter.
    • A closed workstream gives the conductor status and metadata only:
      • fleet_find and fleet_recall never return its sessions, not even a count of withheld matches;
      • fleet_summary gives status only, and machine_map leaves out its branches;
      • a worker of a closed workstream reports that it finished, not what it found.
    • Episodes from before workstreams are judged by their session's recorded home.
    • An orchestrator's own children (same workstream) still see their results. The conductor's tenant-wide board withholds them.
  • Acting:
    • fleet_spawn takes workstream and is judged as a create is:
      • a folder with no workstream, or one whose remote changed, must name one;
      • an exclusive workstream's folder, or an ask workstream's, needs confirming;
      • a closed workstream's folder never takes another workstream's worker;
      • a closed workstream's workers stay in its own folders.
    • The check runs before queueing, and again at claim.
    • Confirmation (G3): the card names the workstream it would pass. Only the session owner's own approval of that card adds a one-time token to that very call. Its handler accepts the token only for exactly the verdict and config revision the card showed. The revision is stored on the task and checked at claim.
    • Every send-class card names its workstream: web, Telegram, the TUI, codeoid approve and the terminal.
  • Egress: workstreams/egress.ts planEgress is a tested stub (G6). A message names its workstream and is sent by a worker homed there.
  • Fixes:
    • The conductor now recalls its own episodes under isolation.
    • The fleet rail docks from md up, so a phone's approval card is no longer covered.

Audits

Two deep rounds, folded in.

  • Round 1:
    • untagged pre-workstreams text was shown;
    • digests were withheld from same-workstream collaborations;
    • an unnamed spawn skipped the remote-changed guard;
    • an ask folder needed no confirmation;
    • delegated tokens could confirm, and the CLI didn't show the card's workstream;
    • query results leaked through counts.
  • Round 2: a confirmation wasn't one-to-one with its card. Stale or denied approvals, approval-time revisions and leftover confirmations could all count. Now it's a token on the approved call. Round 2 also caught a URI-based "conductor-created" check, and confirmations from non-owner members.

Known limits (§12.2):

  • No session cards exist yet (memory/cards.ts has no caller), so a closed workstream gives status rather than a rolling summary. This is on the backlog.
  • Closing a workstream doesn't reach back: what the conductor was told before stays in its transcript and on fleet_tasks.
  • The rail's board has no phone view.

Verification

  • bun run typecheck and bun run lint are clean.
  • Unit: 6158 pass and 1 fails. The failure is inside real bubblewrap > conversations are the session's own, which also fails on main on this machine.
  • Web: the build is clean. 39 tests fail in auth-refresh, embed-refresh, handoff and prompt-drafts, the same set as before; the new ApprovalBar test passes.
  • e2e: 23/23. workstreams-w4.spec.ts drives a real conductor's fleet_spawn card. It has screenshots at 375×667, 390×844, 768×1024, 1280×800, 1470×830, 1920×1080 and 1280×300, with on-screen geometry asserted, and I opened and checked each one.

🤖 Generated with Claude Code

saucam and others added 4 commits November 12, 2026 09:37
… a closed one's text, and acts through workers homed where the work belongs

- fleet tools name each result's workstream and take a workstream filter;
  a closed workstream's episode text never reaches fleet_find, fleet_summary,
  fleet_recall or a worker's digest (status only)
- fleet_spawn takes workstream: judged before queueing as a create is
  (a folder with none needs one; an exclusive folder is an override the
  approval card says; a closed one's folder never), stored on the task and
  judged again at claim
- every send-class approval card (web, Telegram) names the workstream it
  acts in, with the override warning approving it confirms
- egress stub: planEgress (a message is sent by a worker homed in its workstream)
- the conductor recalls its own episodes under isolation
- the fleet rail docks from md up, so a phone's approval card has the width

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on, confirmations tied to the card the owner approved

- untagged episodes are withheld by their session's workstream (live or
  stored), unknown ones while any workstream is closed; a closed one's hits
  are dropped from fleet_find / fleet_recall before anything is counted;
  machine_map leaves out its branches
- a worker's digest is withheld from the conductor only; the conductor's
  tenant-wide task board withholds an orchestrator's closed results
- an override or an ask folder counts as confirmed only by the owner's own
  approval of that very card, once, at that config revision (checked again
  at claim); a delegated token can't approve such a card
- an unnamed spawn keeps the remote-changed guard; a closed workstream's
  worker works only in its own folders
- codeoid approve and the terminal show the card's workstream

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n on the approved call, checked against exactly what its card showed

- the owner's own approval of a card that needed confirming adds a token to
  that call's input; its handler accepts it once, for the tenant, workdir,
  workstream, conflicting workstream, ask folders and revision the card was
  judged on (no leftover, stale or denied approval confirms anything)
- only the session owner's own token confirms (not another member's)
- the warning names the workstream it passes; the TUI shows it too
- conductor tasks known by the routing rule (no goal), not today's URI
- a stored session judged by its recorded home; answers memoised briefly
- an unnamed spawn's claim judged as when queued; a closed workstream with
  only remote rules may work in a folder it matches

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@saucam
saucam force-pushed the feat/workstreams-w3 branch from 984efdd to 276f933 Compare November 12, 2026 01:37
@saucam
saucam force-pushed the feat/workstreams-w4 branch from 6616293 to 0f5887b Compare November 12, 2026 01:37
@saucam
saucam merged commit 1e01258 into feat/workstreams-w3 Nov 12, 2026
2 checks passed
saucam added a commit that referenced this pull request Nov 12, 2026
… a closed one's text, and acts through workers homed where the work belongs (#465)

* feat: workstreams W4 — the conductor reads across workstreams without a closed one's text, and acts through workers homed where the work belongs

- fleet tools name each result's workstream and take a workstream filter;
  a closed workstream's episode text never reaches fleet_find, fleet_summary,
  fleet_recall or a worker's digest (status only)
- fleet_spawn takes workstream: judged before queueing as a create is
  (a folder with none needs one; an exclusive folder is an override the
  approval card says; a closed one's folder never), stored on the task and
  judged again at claim
- every send-class approval card (web, Telegram) names the workstream it
  acts in, with the override warning approving it confirms
- egress stub: planEgress (a message is sent by a worker homed in its workstream)
- the conductor recalls its own episodes under isolation
- the fleet rail docks from md up, so a phone's approval card has the width

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: workstreams W4 audit round 1 — untagged text judged by its session, confirmations tied to the card the owner approved

- untagged episodes are withheld by their session's workstream (live or
  stored), unknown ones while any workstream is closed; a closed one's hits
  are dropped from fleet_find / fleet_recall before anything is counted;
  machine_map leaves out its branches
- a worker's digest is withheld from the conductor only; the conductor's
  tenant-wide task board withholds an orchestrator's closed results
- an override or an ask folder counts as confirmed only by the owner's own
  approval of that very card, once, at that config revision (checked again
  at claim); a delegated token can't approve such a card
- an unnamed spawn keeps the remote-changed guard; a closed workstream's
  worker works only in its own folders
- codeoid approve and the terminal show the card's workstream

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: workstreams W4 audit round 2 — a confirmation is a one-time token on the approved call, checked against exactly what its card showed

- the owner's own approval of a card that needed confirming adds a token to
  that call's input; its handler accepts it once, for the tenant, workdir,
  workstream, conflicting workstream, ask folders and revision the card was
  judged on (no leftover, stale or denied approval confirms anything)
- only the session owner's own token confirms (not another member's)
- the warning names the workstream it passes; the TUI shows it too
- conductor tasks known by the routing rule (no goal), not today's URI
- a stored session judged by its recorded home; answers memoised briefly
- an unnamed spawn's claim judged as when queued; a closed workstream with
  only remote rules may work in a folder it matches

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: the W4 e2e follows the warning that names the workstream passed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
saucam added a commit that referenced this pull request Nov 12, 2026
…s by share policy (#464)

* feat: workstreams W3 — a workstream's folders, a session's read fence, share verdicts for references and moves

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: workstreams W3 — the read fence: sandbox roots and hidden paths from the session's workstream, file tools refused in another workstream's folders

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: workstreams W3 — episodes record their workstream; recall, timeline and get_episode keep to the home and its references

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: workstreams W3 — references at create and mid-session by share policy, moving a session to another workstream

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: lint — no assignment in an expression

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: workstreams W3 — CLI and web UI for references and moves; docs

- codeoid new --ref, codeoid workstream ref add|rm, workstream move
- the session header's chip opens what the session reads and a move; the
  new-session dialog offers what it may read (under isolation)
- a fresh session's sandbox no longer says its directories changed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: lint — plain strings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: a test's memory stub has the session scope W3 registers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: workstreams W3 audit round 1 — every other workstream's folder is fenced, with what the session reads carved out; its own workdir always readable

- fenceFor: no foreign folder is dropped for being nested in the home's
  or holding it; WorkstreamFence.readable (workdir, home, exactly what a
  reference grants) is carved out — the gate checks it, the sandbox hides
  every other entry on the way (carveOut), else the folder whole
- the home is never a workstream's folder, nor a repository top above it
- a session's own workdir stays readable after a move or an override
- extraction: a glob's partial last segment, ACP locations, pi's @ and
  find/ls; a relative path with an unknown cwd is refused
- an added sandbox dir inside a fenced folder is refused and never kept
- reference inputs held to the binding's own bounds; an unreadable stored
  binding seals the session (no home, never moved)
- the session map and the frozen memory index keep to the workstream
- the chip's panel takes focus and follows the window

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: workstreams W3 audit round 2 — monorepos keep to the package, granted paths where they really are, changes at the turn boundary

- a session's repository top is its own only where no other workstream's
  folder holds it; a recorded home widens to its top only where no other
  workstream has a folder inside it; folders are no longer collapsed
- reference paths resolved at grant, and counted only where they really
  are inside the granting workstream (no .., no link planted later)
- Session.setWorkstream saves at once and applies at the next turn's start
- a move adds the old home to readFrom; a home in another workstream's
  folder is judged by that one's share (closed refuses); the claim and the
  bounds are judged again when an ask is answered
- the gate's folder forms are prepared once per fence; a session's own
  folder may be written though a reference holds it
- globs judged at their start and their dir; pi's @ only before a path

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: workstreams W4 — the conductor reads across workstreams without a closed one's text, and acts through workers homed where the work belongs (#465)

* feat: workstreams W4 — the conductor reads across workstreams without a closed one's text, and acts through workers homed where the work belongs

- fleet tools name each result's workstream and take a workstream filter;
  a closed workstream's episode text never reaches fleet_find, fleet_summary,
  fleet_recall or a worker's digest (status only)
- fleet_spawn takes workstream: judged before queueing as a create is
  (a folder with none needs one; an exclusive folder is an override the
  approval card says; a closed one's folder never), stored on the task and
  judged again at claim
- every send-class approval card (web, Telegram) names the workstream it
  acts in, with the override warning approving it confirms
- egress stub: planEgress (a message is sent by a worker homed in its workstream)
- the conductor recalls its own episodes under isolation
- the fleet rail docks from md up, so a phone's approval card has the width

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: workstreams W4 audit round 1 — untagged text judged by its session, confirmations tied to the card the owner approved

- untagged episodes are withheld by their session's workstream (live or
  stored), unknown ones while any workstream is closed; a closed one's hits
  are dropped from fleet_find / fleet_recall before anything is counted;
  machine_map leaves out its branches
- a worker's digest is withheld from the conductor only; the conductor's
  tenant-wide task board withholds an orchestrator's closed results
- an override or an ask folder counts as confirmed only by the owner's own
  approval of that very card, once, at that config revision (checked again
  at claim); a delegated token can't approve such a card
- an unnamed spawn keeps the remote-changed guard; a closed workstream's
  worker works only in its own folders
- codeoid approve and the terminal show the card's workstream

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: workstreams W4 audit round 2 — a confirmation is a one-time token on the approved call, checked against exactly what its card showed

- the owner's own approval of a card that needed confirming adds a token to
  that call's input; its handler accepts it once, for the tenant, workdir,
  workstream, conflicting workstream, ask folders and revision the card was
  judged on (no leftover, stale or denied approval confirms anything)
- only the session owner's own token confirms (not another member's)
- the warning names the workstream it passes; the TUI shows it too
- conductor tasks known by the routing rule (no goal), not today's URI
- a stored session judged by its recorded home; answers memoised briefly
- an unnamed spawn's claim judged as when queued; a closed workstream with
  only remote rules may work in a folder it matches

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: the W4 e2e follows the warning that names the workstream passed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
saucam added a commit that referenced this pull request Nov 12, 2026
…nd credentials, and no backend holds a secret (#462)

* feat: workstreams W1 — named credentials behind a pluggable secret store; a home's env and git identity

- credentials[] declares a credential's id and kind (bearer, header, env,
  oauth) — never its value; every reference across the config is checked at
  load (declared once, of a kind that fits)
- credentialStore picks where values live: env-file (CODEOID_CRED_<ID>, kept
  out of every agent subprocess by the CODEOID_ prefix) or dir (one file per
  id: a mounted Kubernetes Secret, a Vault sink; read-only, non-blocking,
  registered as a secret location)
- a session gets its home workstream's env (values read from the store when
  it is built) and git identity; GIT_AUTHOR_*/GIT_COMMITTER_* pass the
  extra-env GIT_ deny, nothing else does; a workstream env name codeoid owns
  for the backend fails the config load

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: workstreams W1 — isolation: a session reaches only its home's MCP servers, and no backend holds a secret

With workstreams.isolateGlobalServers on (off by default: nothing changes):
- a session's MCP view lists built-ins, the servers its home selects by name,
  and the folder's own servers only when the folder is the home's; no home,
  the conductor's, or an orphaned one: built-ins only — on every backend
- Claude runs with a strict MCP config (no ~/.claude.json, .mcp.json, plugin
  or agent servers of its own); every server it may reach is an in-process
  bridge to the daemon's hub, so it holds no server config, command or secret
- other CLI backends mount natively only servers that need no secret; HTTP
  servers called with a credential go through the daemon's relay, which now
  takes the session's view; a stdio server needing env values is withheld
- a server a workstream selects with a named credential carries a reference;
  the hub and relay read its value from the secret store per request and
  refuse without it
- every hub list and call, and every relay request, is checked against the
  grant as it is now (G4): a removed server fails mid-turn with the reason
- under isolation a user's create in a folder with no home is refused with
  workstream_required; sessions the daemon starts there get built-ins only

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: workstreams W1 — confirmed overrides, the folder record, a home required under isolation

- a create choosing a workstream past another's exclusive claim is refused
  with what an override would change and, when the claimant allows it, a
  token: single use, five minutes, bound to the caller, folder, choice and
  config revision (G3); the confirming retry needs session:approve on an
  undelegated token and is audited (workstream.override); codeoid new shows
  the warning and asks, or --override confirms it
- how each folder last resolved is recorded per tenant: a repository rule that
  now matches another workstream (an edited origin) makes the next create
  choose, and leaves a session the daemon starts there without a home
- under isolation a user's create in a folder with no home is refused with
  the workstreams to choose from

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: workstreams W1 — OAuth credential slots: a second sign-in to one server, one per workstream

- mcp_oauth_credentials gains a credential slot in its primary key, rebuilt
  once in one transaction; every existing row is the default slot ('')
- the OAuth engine reads, refreshes, stamps and signs out per slot; a pending
  sign-in carries its slot, so a completion always lands in the slot it began
  for
- a workstream's server with an oauth credential is called with that slot's
  sign-in (on the hub, the Claude bridge and the relay), never the default
- mcp.oauth.begin / disconnect and codeoid mcp signin --credential take a slot,
  refused unless it is declared as an oauth credential for that server

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: workstreams W1 — codeoid workstream import, an e2e journey, and the docs

- codeoid workstream import proposes workstreams from the repositories on
  disk (grouped by remote owner) and lists Claude's global MCP servers to
  assign; it writes nothing
- skillDirs are accepted but not applied yet, and the daemon says so
- e2e: under isolation a create with no home is refused with the choices; an
  exclusive folder takes only a confirmed override; credentials show by name
- docs/CONFIGURATION.md: credentials, the secret store, what a workstream
  gives its sessions, overrides, moving an existing setup; the design's
  backlog and status

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: workstreams W1 audit round 1 — CLI backends kept to codeoid's servers, no daemon variable in a server config, an honest folder record

Security:
- Codex, the Gemini CLI and Qwen loaded MCP servers of their own under
  isolation. Codex now has every server its user and project configs declare
  turned off (with a placeholder transport, so an untrusted project file still
  starts; measured on codex 0.166) and its apps off; the Gemini CLI and Qwen
  get only codeoid's server names allowed; opencode's turns are refused under
  isolation until its project config can be kept out
- ${CODEOID_*}, ${ZEROID_*}, ${TELEGRAM_*} in a server config resolve empty, and
  such a bearerTokenEnv is ignored: a repo's .mcp.json could read any
  workstream's credential from the daemon's environment
- secretFree also reads the URL (a token in its path or query, user:pass@)
  and the command line: such servers go through the relay or are withheld
- Claude under isolation bridges only HTTP servers; a stdio one needing no
  secret is the CLI's to run inside the session's sandbox, as before (the hub
  would have run a repo's script on the host)
- a delegated token can't edit an origin and then choose the workstream it
  now matches, and never writes the folder record
- the hub's per-call check refuses an unknown session under isolation; a
  relay whose credential changed is closed (the backend initializes again);
  a slot sign-in or sign-out drops that slot's hub clients too
- override tokens go only to callers who can redeem them

Correctness:
- the folder record holds what the folder matched, never a chosen home, and
  is written once the session exists: an explicit choice no longer makes the
  next plain create in that folder refuse
- an override token is spent only once its session exists
- not isolated, the relay reaches exactly what it did before
- a server address change rebuilds the Claude query
- servers withheld from a backend are said in the session, once per set
- --override without --workstream is refused with why

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: workstreams W1 audit round 2 — codex plugins off, rejecting a changed origin sticks, the daemon-variable rule narrowed

- codex plugins bring MCP servers that can't be turned off by name: under
  isolation plugins are off whole (and /etc/codex layers are read too)
- choosing a workstream other than the folder's match (rejecting a changed
  origin included) no longer records the new match; the record changes only
  when the owner accepts the match
- codex's home config.toml is read through a link (account overlays link it;
  isolation failed to start for every codex account); a project's is not
- a codeoid server named like one of codex's own entries is refused: codex
  merges the two, lending codeoid's server that entry's headers or env
- the rule against naming daemon variables in a server config is narrowed to
  workstream credentials (CODEOID_CRED_*): a user's own CODEOID_* variable in
  their own config resolves as before
- an override token is spent up front again: single use under concurrent creates
- tests: codex plugins/links/merged names, the Gemini/Qwen allowlist, the
  opencode refusal, the record after a rejected change

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: codex isolation holds a config to well-formed table headers, never to the runtime parser's leniency

Bun 1.3.13 (the pinned runtime) reads an unclosed header such as
`[mcp_servers.x` as a table where 1.4 refuses it, so on CI a malformed
codex config started codex instead of being refused. tomlStructureProblem
checks every table header (outside strings and open values) and that
nothing is left open, before the parse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: workstreams W2 — edit workstreams and credentials live, in the web UI (#463)

* feat: workstreams W2 — edit workstreams and credentials live

- workstream.upsert / delete / settings and credential.list / upsert /
  delete / set_value: settings:write on an undelegated token; written to
  config.json through the settings store (the whole config validated, nothing
  written otherwise) and applied to the running daemon at once — new sessions
  and every session's grant follow it, live sessions keep their homes;
  workstreams.changed announces it
- a credential's value goes to .env (0600) and the daemon's environment and
  is never sent back: only whether it is set, and where; an OAuth one points
  at codeoid mcp signin; deleting one a workstream uses is refused
- a session's MCP view fingerprint includes its grant: a workstream edited
  rebuilds that session's backend at its next turn, on every backend

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: workstreams W2 — the web UI: Workstreams and Credentials in Settings, a picker and override confirm in New session, a chip on every session

- Settings → Workstreams: isolation switch, default, the list (claim, share,
  rules, servers, env), an editor that keeps what it doesn't show (the item
  as configured, not bases merged — WorkstreamSummary.config), remove, and
  'check a folder'
- Settings → Credentials: declare by id and kind, set a value from a password
  field (sent once, cleared, never shown back), clear, remove (refused while
  used), OAuth slots point at codeoid mcp signin
- New session: what the folder resolves to and why, an explicit choice; a
  choice past an exclusive folder is offered with what it would change, and
  Start anyway resends with the override token
- a workstream chip in the session header and the session list (orphaned red,
  override amber); the list filter matches workstream names
- live: workstreams.changed refetches what's shown

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: workstreams W2 — the UI journey with screenshots at every viewport; fixes they showed

- e2e: Settings lists and adds a workstream, a credential value is set and
  never shown back, the new-session dialog says what the folder resolves to,
  a choice past an exclusive folder is offered and confirmed, and the chip
  shows it — screenshots at 375x667, 390x844, 768x1024, 1280x800, 1470x830,
  1920x1080 and 1280x300, the override's buttons asserted inside the window
- the Default select no longer overflows a phone's width; the override offer
  scrolls into view as it appears and says what the choice gets accurately

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: workstreams W2 audit round 1 — edits apply both blocks, live env, a header is part of a credential, editor survives refreshes

Daemon:
- an edit applies both the workstreams and the credentials block from the
  file as written: a hand edit since boot is never shadowed by a stale copy
- a session's workstream env is read live: a credential set, swapped or
  cleared, an env entry removed, reaches the backend at its next start, and
  a changed env rebuilds the backend at the next turn like its servers
- a credential's header is part of its hub client and of the per-call check:
  redeclaring it with another header is another credential
- credential.delete takes a strict id and refuses an undeclared one; deleting
  an OAuth slot signs it out; a value set in the daemon's real environment is
  refused (it would win at the next start); an edit's reply needs no
  settings:read; the revision covers credentials too

Web:
- workstream rows are keyed by id: an open editor keeps what was typed when
  the list refreshes (another client's edit, a toggle)
- a forced refresh during a fetch refetches after it, so the newer list wins
- the editor checks env and server lines before sending; failed settings saves
  put the control back; remove and clear confirm, with Cancel, busy-guarded;
  the credential field uses new-password
- New session: the last folder's answer never shows for a new one, a failed
  check says so, the override offer names workstreams as the picker does and
  clears on a mode switch, focus moves to create after choosing the folder's,
  and workstream_required is said in the UI's words with the picker focused

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: workstreams W2 audit round 2 — env changes rebuild a backend without MCP servers, shadowed .env values refused, an OAuth slot's removal signs every tenant out

- A session with no MCP view never rebuilt its backend when its workstream's
  env changed: #applyMcpView returned early. The env check now runs before
  the view check, and #envBuiltWith keeps a sha256, not the values.
- loadDotEnv records the keys the real environment shadowed; set_value
  refuses those too, as it did a key set only in the real environment.
- Removing an OAuth credential forgets its slot for every tenant and URL
  (Store.clearMcpOAuthSlot, McpOAuth.forgetSlot), never the default sign-in.
- Credentials rows keyed by id, so a refresh keeps a typed value.
- New-session dialog: the workstream picker in collaborative mode; pipeline
  runs get UI-worded workstream errors (showCreateError).
- The dialog's deferred focus no longer moves focus already inside it
  (a fast fill typed the workdir into the name: the e2e flake).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* feat: workstreams W3 — read fence, scoped recall, references and moves by share policy (#464)

* feat: workstreams W3 — a workstream's folders, a session's read fence, share verdicts for references and moves

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: workstreams W3 — the read fence: sandbox roots and hidden paths from the session's workstream, file tools refused in another workstream's folders

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: workstreams W3 — episodes record their workstream; recall, timeline and get_episode keep to the home and its references

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: workstreams W3 — references at create and mid-session by share policy, moving a session to another workstream

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: lint — no assignment in an expression

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: workstreams W3 — CLI and web UI for references and moves; docs

- codeoid new --ref, codeoid workstream ref add|rm, workstream move
- the session header's chip opens what the session reads and a move; the
  new-session dialog offers what it may read (under isolation)
- a fresh session's sandbox no longer says its directories changed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: lint — plain strings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: a test's memory stub has the session scope W3 registers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: workstreams W3 audit round 1 — every other workstream's folder is fenced, with what the session reads carved out; its own workdir always readable

- fenceFor: no foreign folder is dropped for being nested in the home's
  or holding it; WorkstreamFence.readable (workdir, home, exactly what a
  reference grants) is carved out — the gate checks it, the sandbox hides
  every other entry on the way (carveOut), else the folder whole
- the home is never a workstream's folder, nor a repository top above it
- a session's own workdir stays readable after a move or an override
- extraction: a glob's partial last segment, ACP locations, pi's @ and
  find/ls; a relative path with an unknown cwd is refused
- an added sandbox dir inside a fenced folder is refused and never kept
- reference inputs held to the binding's own bounds; an unreadable stored
  binding seals the session (no home, never moved)
- the session map and the frozen memory index keep to the workstream
- the chip's panel takes focus and follows the window

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: workstreams W3 audit round 2 — monorepos keep to the package, granted paths where they really are, changes at the turn boundary

- a session's repository top is its own only where no other workstream's
  folder holds it; a recorded home widens to its top only where no other
  workstream has a folder inside it; folders are no longer collapsed
- reference paths resolved at grant, and counted only where they really
  are inside the granting workstream (no .., no link planted later)
- Session.setWorkstream saves at once and applies at the next turn's start
- a move adds the old home to readFrom; a home in another workstream's
  folder is judged by that one's share (closed refuses); the claim and the
  bounds are judged again when an ask is answered
- the gate's folder forms are prepared once per fence; a session's own
  folder may be written though a reference holds it
- globs judged at their start and their dir; pi's @ only before a path

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: workstreams W4 — the conductor reads across workstreams without a closed one's text, and acts through workers homed where the work belongs (#465)

* feat: workstreams W4 — the conductor reads across workstreams without a closed one's text, and acts through workers homed where the work belongs

- fleet tools name each result's workstream and take a workstream filter;
  a closed workstream's episode text never reaches fleet_find, fleet_summary,
  fleet_recall or a worker's digest (status only)
- fleet_spawn takes workstream: judged before queueing as a create is
  (a folder with none needs one; an exclusive folder is an override the
  approval card says; a closed one's folder never), stored on the task and
  judged again at claim
- every send-class approval card (web, Telegram) names the workstream it
  acts in, with the override warning approving it confirms
- egress stub: planEgress (a message is sent by a worker homed in its workstream)
- the conductor recalls its own episodes under isolation
- the fleet rail docks from md up, so a phone's approval card has the width

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: workstreams W4 audit round 1 — untagged text judged by its session, confirmations tied to the card the owner approved

- untagged episodes are withheld by their session's workstream (live or
  stored), unknown ones while any workstream is closed; a closed one's hits
  are dropped from fleet_find / fleet_recall before anything is counted;
  machine_map leaves out its branches
- a worker's digest is withheld from the conductor only; the conductor's
  tenant-wide task board withholds an orchestrator's closed results
- an override or an ask folder counts as confirmed only by the owner's own
  approval of that very card, once, at that config revision (checked again
  at claim); a delegated token can't approve such a card
- an unnamed spawn keeps the remote-changed guard; a closed workstream's
  worker works only in its own folders
- codeoid approve and the terminal show the card's workstream

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: workstreams W4 audit round 2 — a confirmation is a one-time token on the approved call, checked against exactly what its card showed

- the owner's own approval of a card that needed confirming adds a token to
  that call's input; its handler accepts it once, for the tenant, workdir,
  workstream, conflicting workstream, ask folders and revision the card was
  judged on (no leftover, stale or denied approval confirms anything)
- only the session owner's own token confirms (not another member's)
- the warning names the workstream it passes; the TUI shows it too
- conductor tasks known by the routing rule (no goal), not today's URI
- a stored session judged by its recorded home; answers memoised briefly
- an unnamed spawn's claim judged as when queued; a closed workstream with
  only remote rules may work in a folder it matches

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: the W4 e2e follows the warning that names the workstream passed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant