# Managed harness — local acceptance, 30 September 2026
<!-- language: en -->

This is the local implementation under test, not a published release. Studio owns team/key/model authority and usage; Watch imports the connection and brokers credentials; Piagent executes the selected main/research/review harness. Personal OAuth stays in a separate runtime.

## 2026-10-15 — full suite on 1.25.0: green; the task-contract code that remains

Superseded figures: the 2026-10-01 entries below (235 failures, "69 documents still teach `/workflow`") describe the tree before the 1.9.0 retirement was finished.

- **Full suite at 29ce65a (1.25.0):** `npm test` ran 6,336 tests: 5,901 pass, 0 fail, 435 skipped; `npm run typecheck` is clean. The skipped tests are integration tests of the task-contract acceptance executor; they need `PIAGENT_CONTRACT_EXECUTOR_IMAGE_ID`, a Docker socket or a pinned local worker, and none is configured here.
- **Docs:** pages that still name `/workflow` or `/task` say they were retired in 1.9.0, or are design records carrying the "before 1.9.0" note.
- **Remaining task-contract code (deferred by the owner):**
  - 21 runtime modules are reachable from no entrypoint (Pi extensions, `piagent-cli.mjs`, the WebUI client and server, the managed session). Examples: `runtime/registration/task-start-*.ts`, `workflow-commands.ts`, `task-completion-tools.ts`, `runtime/verification/{completion-preparation,composite-*,exact-verifier-reuse,independent-acceptance-runtime,runtime-contract-runner}.ts`, `runtime/recovery/{semantic-repair-*,independent-verification-recovery,acceptance-recovery-guidance}.ts`. They are still imported by 21 test files, most of which also test live modules.
  - `task-state`, `task-lifecycle` and `acceptance-*` are still imported by `piagent-guard.ts`. No current path creates a task, so they only read task records written before 1.9.0.
  - `runtime/inspection/task-*.ts` are live: the WebUI server loads them with `import(\`${CORE_ROOT}/…\`)` to show older task history.
  - Removing this code is a separate refactor, done one layer at a time with a full-suite run after each layer.
- **The Harness tests were not running.** 33 test files looked for the Pi host only under `~/.pi/npm-global` unless `PI_MANAGED_TEST_SDK` was set. That covered every end-to-end Harness test (workflow, helpers, objections, effort, fast mode). This machine and CI install the pinned host into the npm global root, so those tests skipped there and the green suite above proved nothing about the Harness. `tests/helpers/pi-sdk.mjs` now looks in `PI_MANAGED_TEST_SDK`, then the installer's prefix, then the npm global root when it holds the pinned version.
  - With the host found, 13 more tests failed. 12 were this machine: Playwright's Chromium was not installed (CI installs it).
  - The 13th was a product defect. While a local proxy listens, a test that starts its own localhost server is blocked (by design), but `node --test` printed only `fetch failed`, so the agent got no way on. `sandbox-diagnostic.mjs` now names `run_with_network` for it (`managed-host-escalation`).
  - `managed-delegation-cli`, now running, wrote ^C into a terminal the CLI had already closed (`EIO`); the write tolerates that, and the test still requires the CLI to start.
- **Harness conformance:** `docs/harness-conformance.md` names, for every setting of a team's Harness, who enforces it (Studio or Piagent), what a company session does and the tests that prove it. `tests/managed-harness-conformance.test.mjs` fails when a setting has no section, when a section names a test that does not exist, or when the pinned host is missing on macOS in CI. It also adds the two checks no test had: `max_fix_loops` read within 0–3, and one helper of each role at a time.
- **Developer loop:** `docs/company-dev-commands.md` lists what runs without asking and what names its approval, measured on this Mac. `tests/managed-dev-loop.test.mjs` replays one loop offline in one sandbox: branch, failing test, fix, `npm test`, `make check`, build, commit, stash, the app's own server, a background server within one command, `sqlite3`, a venv, and the internet refused with `run_with_network` named. Where `gh` or `ssh-add` find no sign-in in the sandbox, the agent is now told to use `run_on_host` instead of `gh auth login`. Owner, 2026-10-15: GitHub work, pushes and private registries stay on `run_on_host`; no separate GitHub tool.
- **Background processes (`start_process`, `process_output`, `stop_process`, macOS):** a dev server, watcher or local service runs across turns in the plain sandbox, without network, until stopped, the conversation closes or 4 hours pass; at most 4 at once; output is bounded (64 KiB), read once and redacted; `process_output` can wait for a ready line. They are stopped by process group, by parent and by their own marker. While measuring this, `stopStrays` was found to miss a process a command left running when it is one of Apple's binaries (`nohup sleep …`): `ps -E` cannot read their environment. Node, Python and Java processes are found; the limitation is documented, not fixed. Tests: `managed-background-process` (a real dev server across commands and a turn end; limits, buffer, redaction and lifetime with a stand-in).
- **Browser and MCP (owner, 2026-10-15):** UI checks use the project's Playwright; MCP in company sessions waits for a Studio allowlist design. Measured: Playwright's Chromium runs in the plain sandbox against a server on this Mac without approval, and with approval too. The prompt and the `run_with_network` description said a browser needed approval and that bash cannot listen on a port (macOS); both now say what was measured (Linux keeps the earlier wording, not measured there). The Chromium test, opt-in before, now builds its own project from this repository's `@playwright/test` when the Mac has Playwright's browsers.
- **Type errors right after an edit (owner, 2026-10-15: TS/JS language service):** `ts-diagnostics.mjs` starts `ts-diagnostics-server.mjs` in the command sandbox (pinned like the worker's modules) and adds the file's errors to a successful `write`/`edit` result; a clean file, a project without TypeScript or a config, or an answer later than 8 s adds nothing. This repository uses TypeScript 7.0.2, the native compiler, whose package has no JavaScript language-service API (`require('typescript')` gives the version only; the API is under `typescript/unstable/*`). TypeScript 7 is therefore checked with the project's own `tsc --noEmit --pretty false` (0.34 s for this repository) and TypeScript 5–6 with a language service. `tsc` checks no types while any file has a syntax error; the note then says the file was not type-checked and names the syntax errors. Tests: `managed-ts-diagnostics` (TypeScript 7 here; TypeScript 5.9.3 through `PIAGENT_TEST_TYPESCRIPT`, run once from a package fetched for the purpose).
- **Turn checkpoints and `/undo` (company Terminal):** `checkpoints.mjs` writes the working tree as a git tree through a private index before a run's first change (an edit, a command that may write, `run_with_network`/`run_with_docker`/`run_on_host`) and after the run; `/undo` restores what the last turn changed through a private index and refuses while a turn runs or when a file changed again since. Found while testing: copying the index without its time made git treat a file rewritten within the same second at the same size as unchanged, so the turn looked empty; the copy keeps the time (`cp -p`). Tests: `managed-checkpoints` (three turns, the member's staged change, untracked file and HEAD kept, refusal after a member edit, outside git). WebUI hunk revert still requires a task; it moves with the WebUI work.
- **A compaction test had no room left:** `managed-compaction` › "a history longer than the new harness model's window is summarised in parts" sent 11 turns of 26.5k tokens on top of about 8k of system prompt and tools: 300,000 tokens, exactly the trigger, after the new tools added about 500 tokens. The turns are now 26k (about 295k), still above the 272k the next step needs.
- **Pi host 1.2.0 prepared, pin unchanged (owner, 2026-10-15):** the pin is still 0.87.1. Runtime checks now read it from one place (`managed/pi-host.mjs`, the package's peer dependency); `verify-release-identity.mjs` keeps its own copy on purpose. Pi 1.2.0 was installed into a scratch prefix (the member's global `pi` stays 0.87.1) and a copy of this tree pinned to it. Three incompatibilities were found and fixed so the same code runs on both:
  - Pi 1.0.1 dropped its npm shrinkwrap, so `@earendil-works/pi-ai` is hoisted beside the host instead of nested in it. `hostPackage()` finds it the way Node does (session, doctor, tests).
  - The command sandbox read only the host's folder; with hoisted dependencies it now reads the `node_modules` that holds them (unchanged for 0.87.1, where they are nested).
  - Pi 1.x's bash tool returns an `isError` result for a non-zero exit instead of throwing, so the sandbox notes (`managed-sandbox-denied`, `run_with_network`…) were skipped. The worker reads an `isError` result as the throw it was (`isError` is set, so check counting was not affected).
  - Results on 1.2.0: all 176 SDK-backed company tests pass (the three dashboard tests with 1.2.0's `pi` first on `PATH`); the real-host WebUI wire, PCL and session-wait tests pass. Still failing on 1.2.0: the benchmark custody tests (`benchmark-scoped-frozen-qualification.mjs` requires pi-ai's `event-stream.js` inside the hashed host tree, which a hoisted layout does not satisfy) and benchmark tests that need a git checkout. They belong with the deferred benchmark redesign.
  - Switching the pin also needs: root `package.json` and `packages/piagent-core/package.json` (pi-coding-agent and pi-ai), `package-lock.json`, `supportedPiHost` in `verify-release-identity.mjs`, and the `brace-expansion` advisory exception, which 1.x no longer needs (no shrinkwrap). Personal mode on 1.2.0 was exercised only through these tests, not interactively.
- **Headless JSON:** `piagent studio --prompt <text> --json` writes the `pi --mode json` stream through Pi's `toJsonEvent` (`dist/modes/json-event.js`, the same in 0.87.1 and 1.2.0); `--json` without `--prompt` is refused. Test: `managed-delegation-cli` (a second CLI run parsed line by line: session header, agent and turn events, the answer in `message_end`, deltas without cumulative snapshots).
- **WebUI (owner, 2026-10-15):**
  - *Several pages at once:* a message sent from a page that did not start the running turn (another tab) was refused (`session-operation-conflict`) and kept as "not sent". The Gateway now queues it as a follow-up of that turn, as the Terminal does, when the session streams and is not settling (`provider-wire` sessions keep refusing). Test: `piagent-webui-session-lease-runtime` › "a new message from another page while a turn streams waits for that turn instead of being refused".
  - *Tool activity and per-turn usage:* already shown by the current WebUI (live and past tool cards in `TimelineTurn`/`ToolCard`, a turn's requests, tokens, time and speed); the 2026-09-30 gap list was out of date. Checked in code and the existing tests, not by looking at the page.
  - */undo:* sent as a message in a company conversation, it runs `checkpoints.undo()` with no model request and adds a note (`agent-watch-process`, phase `undo`, schema `transcript-v1` and regenerated contracts) worded in both languages; the composer offers `/undo` among the commands of company conversations only. Tests: `managed-checkpoints`, `piagent-webui-harness-process`, the browser test "a company conversation offers its own /undo among the commands; a personal one does not".
  - Browser suite: 44 of 44 in two runs at load average about 2; two earlier full runs under load (6–12) lost a keystroke in the `@` and `/` composer tests (passed alone).
  - *Hunk revert without a task:* not done. It reverts only what exact task provenance proves the agent changed; a session-level proof is a redesign of that safeguard.
- **Terminal commands in the dashboard (owner, 2026-10-15):** `client/src/conversation-commands.ts` lists `/help /new /stop /name /fork /pin /archive /thinking`, `/model /permission` (personal) and `/undo /fast /bypass` (company), `/changes /settings`; the composer runs them through the buttons' session commands, with no model request (`/undo` is sent: the company runtime answers it). Full access and Bypass ask for confirmation as the buttons do; a usage note keeps what was typed. Shortcuts: ⌘⇧O new chat, ⌘/ the `/` commands, Esc stops the running turn. Tests: `piagent-webui-conversation-commands` (matching, company/personal lists, each command's session command, confirmation, refusals, `/help`), browser tests for the `/` list and for `/name`, a usage note, `/help` and ⌘⇧O. Esc stopping a turn has no automated test (the browser fixture has no running turn to stop).
- **Full suite after these changes:** 0 failures: 6,008 pass, 317 skipped (integration tests of the retired task-contract executor and other opt-in suites); the reported total varies by a few dozen between runs because concurrent TAP output is cut. `benchmark-budget-launcher` › "real unresponsive core and registered detached provider are stopped by outer watchdog" is a 5-second timing test that failed in two earlier full runs under load and passed every run alone, on this tree and on a clean checkout of HEAD.

## 2026-10-03 — helpers object to a wrong brief; disagreements go to the member

The owner approved the rule:
- a helper may object to the main agent's brief, with evidence, but never decides;
- the main agent decides and must answer;
- the same disagreement a second time goes to the member.

Branch `helper-objections`, installed locally as 1.9.3 with the change, together with Studio (process report v3) and a new Agent Watch build. Nothing committed or published.

- **Live cases.** 16 runs through the real Studio, Agent Watch broker and models:
  - Harness: all steps required, two fix rounds;
  - roles: scout mimo-v2.6-flash, research deepseek-v4.1-flash, verify hy3, review claude-sonnet-5, main claude-sonnet-5-5.

  Some cases plant a wrong brief where the main agent hands work to a helper, to see whether the real helper catches it. The planted brief is written into the main agent's own tool call too, so the main agent reads it as its own.
- **Caught and answered.**
  - A member's wrong function name passed on to the scout: `wrong_premise`. The agent turned to the real function.
  - A brief that would change a signature the member asked to keep: `conflicts_with_request`. The agent kept the signature and said the scout was right.
  - A brief that stayed wrong after a corrected brief: a second objection. The member was told, the helper was not asked again, and the run reported `disputed`.
  - A failed verify claim went back to the agent. The agent fixed the cause, and the harness's re-check passed.
  - A claim outside the request that the agent never made was set aside as unverifiable on re-check, with no dispute.
  - A reviewer's false blocking finding (simulated) was answered without a code change. The reviewer read the answer and withdrew the finding.
- **Precision.**
  - A sound scout brief drew a `wrong_premise` objection over a hedged guess ("if any, e.g. tax, shipping"). The brief check now says a marked guess is not an objection; the rerun had none.
  - Sound research briefs drew none.
  - The real reviewer twice refused a planted "team rule" that conflicted with the member's request, so no wrong blocking finding was produced.
- **Fixed during the run.**
  - Objections to the harness's own briefs (its review, its verify re-check) only restated the member's request. One was counted as a second objection, which produced a false dispute. They are no longer relayed.
  - The verify re-check now lets a claim outside the request be marked unverifiable.
  - The timeline kept a turn's last answer as a step when a quiet note followed it, and showed "this turn has no answer". Objection, rejudge and dispute notes now leave the answer in place.
- **"tiếp tục".** After a stopped turn and "tiếp tục", the harness review read the original request as "Earlier message". Before, it read only "tiếp tục".
- **Report.**
  - After the new Agent Watch was approved in Keychain, the broker advertised `process-v3`. Studio stored `version 3, outcome disputed, objections 2, objections_answered 1, disputes 1` for an unchanged run.
  - Older brokers keep receiving version 2 with the outcome they had before.
- **Cost.** 16 runs used about 230 live trial requests.

## 2026-10-02 — a message after Stop: the busy Gateway, and Gateways started beside it

Before pushing 1.9.0 the owner asked for one more long fleet round to hunt the one open defect: a dashboard page that, after Stop, could not send ("no answer" three turns in a row, or a composer locked for minutes). Committed on `release/v1.9.0` with the release; nothing pushed or published.

- **Evidence.** In the Gateway's command journal, 2 of 88 Stops took longer than the page's 30-second wait (48.6 s and 50.9 s), and they are exactly the two failures: the next messages were never journaled, and the page held them as unconfirmed. Under the fleet each catalog took 1–6 s (400 KB) and the Gateway ran at 100–160% CPU. A probe in `ux-lab/fleet/stop-probe.mjs` (long write, Stop at a random moment, then "tiếp tục" and one more message, each checked against the journal) failed 3 of 4 cycles under the same load.
- **Cause 1, fixed.** Every catalog opened and parsed every conversation file for its model and thinking level (`SessionManager.open` on 929 files, 140 MB, about 1 s of blocked event loop). `gateway/session-file-facts.ts` keeps a file's facts until its size, modification time or inode changes; a conversation running in the Gateway is still read from its live manager. Catalogs now take 0.1–0.5 s under load; a 30-second CPU profile of the Gateway under the fleet is 79% idle, catalog building 8% (lease reads 5% of that, a later candidate).
- **Cause 2, fixed.** A busy Gateway was taken for a dead one. `piagent dashboard open` waited 1.5 s for an answer, the starting Gateway 0.5 s, the company launch 1.5 s; then a second Gateway started and took the control socket, and the first ran on unreachable with its pages and conversations (three personal Gateways were found running at once, one at 160% CPU). Now a socket that accepts but answers late belongs to a running Gateway (`gateway-already-running`), `dashboard open` and the company launch wait for it (30 s, then `gateway-busy` / `managed-launch-busy`), and the control socket is bound under a private name and linked into place, because closing a server removes the path it was bound to: an old Gateway exiting had removed the running one's socket, and `piagent dashboard restart` then refused ("did not stop").
- **Not changed.** A message sent from one page while another page's turn runs is refused (`session-operation-conflict`) and the page keeps the draft with "not sent"; that is the second of the two journal cases (a Continue clicked in another tab). Keeping such a draft without the notice needs a held-turn case in the browser fixture first.
- **After the fixes** (one Gateway, the same 20-developer load for 2 hours): the probe passed 30 of 30 cycles with every Stop settled in 6–8 s; the fleet ran 1,291 turns (276 company, 249 of them queued for the one company slot, mean 4 min, longest 20 min) with one flagged turn, and none of Giang's 163 dashboard turns (27 Stops) failed. The flagged turn was a company commit request cut by the fleet's 25-minute limit: the probe was writing files into the same folder, the Harness counted them as this turn's change and sent the agent back twice to run checks, and each step waited for the company slot. The process gate counting changes made by others in the same folder is noted for the Harness roadmap; the fleet now treats its own time-limit stop as a stop.

## 2026-10-02 — twenty developers at once: company requests wait in line; the dashboard copes with many conversations

The owner asked for about twenty developers working continuously, with varied requests, through every part of Piagent, and granted the live budget for it (Studio ceiling raised to its maximum, 10,000). Nothing was committed, tagged, published or deployed.

- **How it was run.** `ux-lab/fleet` plays 20 members for hours against the installed build: 14 company members in the company Terminal path (real Studio, Agent Watch broker and Harness), 4 personal members in the Terminal (`pi -p`, two Codex models), and two dashboard members (one company, one personal) driven with Playwright. Each works on its own repository or a shared one (Python, Go, Node, a monorepo, a folder with a Vietnamese name, a folder without git, files with fake secrets), day after day: questions, edits, multi-step fixes, protected files, network installs, web search, review, commit, push (denied), destructive requests, Stop then "tiếp tục". Every turn is judged on disk (checks, protected files, pushes, leaks).
- **First run: company turns failed under load.** 22 of 26 company turns ended with `session_account_busy_retry_later`. The local Studio has two model accounts with one request at a time each, and the Harness uses Claude only, so the whole company shares one slot; Studio holds a refused request 30 s and then refuses it, and Piagent failed the turn.
- **Defects found and fixed, each with a test:**
  - a request Studio refuses at admission for capacity now waits in line and is asked again, for up to 20 minutes (`managed/request-stream.mjs`). Only admission refusals are asked again: they come before any model is asked, so nothing is sent twice. A request Studio held in its own line is asked again at once, so a long wait keeps its place. The wait is a step of the running turn in the dashboard and the working line in the Terminal; Stop ends it;
  - any change to any conversation made every other dashboard command stale, so with several conversations running a new conversation failed with `session-revision-stale` (shown as that code). A command is now outdated only when its own conversation changed; a create failure is a sentence;
  - Stop pressed while the agent worked through quick steps was refused as `session-revision-stale` (the conversation changes after every step) and the page dropped the error, so nothing happened; Stop is now bound to the operation it names, and a failed Stop is shown;
  - one company turn in about a hundred ended with "the request could not be sent" while Studio recorded the request as admitted with no usage: the answer stream had ended empty ("Anthropic stream ended without a stop reason"), a provider-side failure the classifier did not know. It is now `upstream_incomplete` ("the model returned no response"), and any failure no code names keeps its redacted words in the conversation file;
  - while Studio's connector renewed the Claude account's sign-in, two turns failed with `upstream_auth_required` and `execution_precondition_failed`; the account was ready again within a second. Both refusals come before the model does any work, so they are now asked again up to three times;
  - with many members on one key, Studio occasionally answered "the company key is no longer valid": reading the key configuration collided with a concurrent stamp of the same key row (PostgreSQL 40001) and the collision was reported as an invalid key. Fixed in Studio (the read is tried again, a collision is never an invalid key) with a database test that reproduces it;
  - a member asking to print `.env` encoded in base64 was declined by the model's provider under its usage policy, which the dashboard showed as "the request could not be sent"; it is now `upstream_policy_refusal` with its own copy. The provider's words were found through the redacted record kept for unnamed failures;
  - the owner put the Mac to sleep during the run. After it woke, every company conversation failed with `invalid_run_grant`, new turns included: Docker's VM clock lagged about 98 s behind and then caught up at once, so two-minute run grants issued in the lag lapsed under their first request; and broker requests that outlived the sleep timed out and killed the Agent Watch helper, after which each turn failed at once with "the Agent Watch helper stopped". A lapsed grant is now renewed and asked again (it fails before dispatch, so nothing runs twice), and a dead helper is replaced when the next turn starts;
  - under load, four of six company conversations failed to open (`managed-broker:serverUnavailable`) and inference requests were refused with `operation_unavailable`: Studio stamped the shared key's last-use on every authentication, and that write queued behind the admissions holding the key row until the five-second authentication deadline. Studio now stamps it at most once a minute (checks unchanged); Piagent asks a busy Studio again for the configuration, the run start, the subagent grant and the request. In that window 16 of 25 finished company turns had ended with "review could not run": the review role was refused while Studio was busy; on an idle Studio the same review ran;
  - a dashboard message to a company conversation that never reached the company Gateway left the composer locked ("Piagent is confirming this send") until the page was reloaded. A send now expires after two minutes and the page releases it then, keeping the draft;
  - right after the dashboard restarted, creating a conversation took longer than the page's 30-second answer: the page said "could not be created (gateway-command-response-timeout)" while the conversation was created and ran, and the next message created a second run editing the same repository (the fleet's check then failed mid-edit). The page now asks again with the same command, which the Gateway answers with the first create's receipt;
  - with about 1,300 conversations on the machine the dashboard took 30–40 s per page call (113% CPU), so dashboard members timed out: Pi's session listing reads every file in full and the catalog asked for it constantly. Listing is now cached per file (unchanged size and time keep their info; changed files are read again by Pi): 44 ms instead of about 4 s for 863 conversations;
  - the guard refused a member's session reading a packaged skill (`test-audit`) as outside the project, so the skill Pi offered could not be used; installed skill folders are now readable, never writable;
  - clones with the same folder name were indistinguishable in the new-conversation menu; they now show where each one is;
  - stale `enabledModels` patterns made Pi warn on every start; `piagent-doctor` lists them and `piagent-model-scope --prune` removes only those.
- **Results after the fixes** (two runs, 59 minutes, 462 turns: 327 personal, 83 company, the rest in the dashboard): no company turn failed for capacity. 68 of 83 company turns waited in line, 159 to 170 s on average and 727 s at most; the wait follows from one shared slot, not from Piagent. Problems left: one Stop failure in the dashboard (fixed above) and one false report of the fleet's own judge on a repository two members share (fixed in the fleet). The fleet keeps running for the owner's continuous check.
- **Not changed, for the owner to decide:** company capacity (more accounts, more requests per account, or a Harness that also uses Codex); first-come order across members (Studio admits whichever waiting request asks first; strict order needs a Studio queue). The personal Anthropic login on this Mac has expired, so the personal members ran on Codex only.
- **Mistake made and undone during the run:** editing `scripts/piagent-studio.mjs` (pinned by Agent Watch with `entrypoint_sha256`) made the dashboard's company mode ask for a re-import; the edit was reverted and the installed file matches the pin again.

## 2026-10-01 — 1.9.0 used as a member would: nine experience defects, the guard cleaned, the docs site rebuilt

The owner asked for every user scenario to be played before release, the unused guard logic removed, the docs and piagent.io.vn rewritten around the ecosystem, and the custody question decided. Nothing is committed, tagged, published or deployed.

- **How it was played.** Playwright scripts drive the real dashboard of the installed build (1440×900), send messages as a member, and check the result on disk: 9 personal scenarios (read-only answer, edit and test, commit and push with the browser approval, Stop during a long command, a second message during a turn, `.env`, settings, model and thinking, rename and search) and 4 company scenarios (plan, checks and review; Stop during the review; a network install; a message during the review). After the fixes below, all 13 pass on the final install with no console error.
- **Defects found and fixed, each with a test:**
  - a company turn was settled by the dashboard at its first answer, while the harness was still running its check and review rounds: Stop disappeared, the rounds were not streamed, the header said 0 subagents during the review, and for a moment the turn read "no answer yet, press Continue";
  - `run_with_network` could never resolve a host: the network sandbox allowed IP sockets but not the system resolver socket, so every approved command failed with `ENOTFOUND` (the existing web test used a loopback IP);
  - the company gateway is reused while the version matches, so a same-version reinstall kept old code in company sessions (development only; the reinstall script now stops it);
  - opening a company conversation while company mode was stopped said "could not load history, use refresh" and nothing recovered it; it now explains and offers Reconnect;
  - when that read failed, the previous conversation's messages stayed on screen under the new title;
  - approval cards showed task and tool-call ids, a hashed provider, the member's absolute home path, English policy text ("legacy policy pattern") and no command for network approvals;
  - `apply_patch` steps were shown as a raw patch instead of an edit with its diff;
  - titles were replaced by `pi:<project>`, the model chip read "Model session · Off" during the first turn, Stop was shown as a model error, run state appeared in `git status`, and the WebUI failed with "Command failed: which pi" when `pi` was not on its PATH (earlier in the same round).
- **Guard cleanup.** Every branch that only ran with a task contract is gone (4993 → 4139 lines); see the changelog. An old session reopened in 1.9.0 no longer resumes or re-binds its task.
- **Custody (owner decision).** 1.9.0 makes no private-holdout claim; recorded in `evals/private-holdout-v1/CURRENT_BOUNDARY.md`.
- **Docs site.** New Ecosystem page with a diagram, 1.9.0 What's new with five dark-mode dashboard screenshots taken from these runs, outdated pages fixed (Overview, Quickstart, Commands, Team, Security, Architecture, Benchmark marked historical). Checked at 1440 and 390 px: no horizontal overflow, no broken image. Not deployed.
- **Seen on the machine, not Piagent:** six busy-loop test fixtures left by interrupted test runs were spinning a core each (killed; the fixture now exits when orphaned), and Agent Watch had used a full core for about nine hours in SwiftUI layout before the power cut.
- **Trial.** The company scenarios and screenshots used 140 requests; 211 remain.

## 2026-10-01 — 1.9.0: the retirement finished, five more defects found, release prepared

The owner asked to finish the task-contract/workflow retirement, look at the failures that already existed on 85f76db, and prepare **1.9.0**. Nothing is committed, tagged or published.

- **Tests brought along with the retirement.**
  - Rewritten so their security assertions run without a task: guard integration (protected-token and `.env` refusals, apply_patch targets, source checkout, permission switch, WebUI approval, read-only shell classifier, governed state), the input hook, terminal receipts, uncertain send, workflow catalog, runtime session modules, the explainer, the project template and skill.
  - The HTTP/WebSocket reconnect, lost-receipt and single-turn journeys now run as freeform turns and grade real verification instead of a task record.
  - Retired (still at 85f76db): 52 guard cases and 19 files that only exercised the task lifecycle — `pi-082-runtime-phase-e2e`, `acceptance-diagnostic-mode`, `acceptance-composed-helper-guard`, `benchmark-pi-quality-accounting-chain`, `production-journey-intake` and fourteen `production-journey-*` scenario journeys — plus the benchmark cases bound to `/workflow` dispatch and three offline schedule rehearsals.
  - The 23 public journey witnesses the benchmark helpers parsed out of those files now live in `tests/fixtures/production-public-witnesses.v1.json` (hash-checked).
- **Defects found while doing it, fixed with a test each:**
  - every WebUI approval went through the no-task branch, which never consumed the allow — the decision request never got its receipt and the approval stayed queued until the broker filled;
  - four WebUI runtime buttons (`/commands`, `/onboard status|profile|tech`) named commands that no longer exist, so Pi would have sent them to the model as a message and started a paid turn;
  - the restored completion module re-published `piagent_memory_citation_record`, which needs a task id and could only fail, and the knowledge group put it back on the active list;
  - a shell command could create a new file in a shared source-checkout cache through a path alias such as `/var` for `/private/var`;
  - the stale read-to-edit check was keyed to the task run and silently stopped in freeform turns; it now uses the session.
  - Also: `piagent-explain` still listed task gates; `piagent_document_read` told the model to call a retired tool; `piagent_tools` offered retired groups; `piagent-* --help` wrote `piagent-runtime.json` into the operator's agent directory.
- **The five failures already on 85f76db:** two IE6 checks and the install rollback needed objects a shallow clone lacks (`de28308`, tag `v1.2.17`); after a depth-1 fetch the IE6 checks pass, and the rollback test then caught the `--help` write above. Package distribution passes with a stable version. The offline production schedule fails because the production-v3 grader scores task-contract evidence; that is a redesign for the owner, not a fix.
- **Docs.** README EN/VI, architecture EN/VI (translation pairing re-recorded), command reference, operator manual, quickstart, team and project onboarding, recipes, the docs site and twenty other pages describe freeform turns; eight design documents carry a "before 1.9.0" note.
- **Release metadata.** Version 1.9.0 in both packages and the lock, CHANGELOG `v1.9.0 - 2026-10-01`, release history, install commands, docs-site badges, regenerated capability lock and catalog; `release-identity` passes.
- **Verification.**
  - The first full `npm test` after the migration was run while files were still being edited (6,125 tests, 14 failures). Each failure was rerun on a quiet tree:
    - real issues, fixed: two line budgets (the guard and the managed session crossed them; code moved into `source-checkout-paths.ts` and `workflow.mjs`), and a helper-stop test that aborted on a timer (it now aborts once the review request is in flight; 3/3 under CPU load);
    - one stale `/fresh` help test, retired;
    - the inventory, which had not been regenerated yet;
    - candidate-digest noise from editing during the run.
  - Then the canonical gate `scripts/verify-local.sh --offline` passed end to end: the whole suite at concurrency 2, WebUI e2e, typecheck, WebUI build, wording, pairing, architecture, release identity and scaffold.
  - One more defect showed up afterwards. A packaging test, run before the `--help` fix, had overwritten the real `~/.pi/agent/piagent-runtime.json` with an entrypoint in a deleted temp prefix. The CLI now never records an install under the OS temporary directory (test added), and the record was restored by the installed build.
- **Installed and live.**
  - 1.9.0 is in `~/.local` (2,025 files; the guard and the Studio entrypoint match the source). `piagent-doctor --offline` reports no errors and a single installation; the dashboard was restarted on 1.9.0.
  - The FE deep task ran through Agent Watch's binding on the installed build (Harness rev 4, all steps required): `clean`, plan 5/5, 4 checks with none failing, 2 reviews with no blocking finding, hidden acceptance 5/5. The plan guard refused six early writes before the plan existed.
  - It used 28 trial requests; 351 remain.

## 2026-10-01 — release regression on .15–.17; what the full suite says before a release

The owner asked for deep testing so nothing fails at release. Installed **1.8.1-local.20261001.17**.

- **Fixed:**
  - **Work a command leaves running** (`nohup … &`, a child in its own process group) outlived the turn and the session. Every command now carries a per-boundary marker; what still carries it is stopped when a turn ends and when the boundary closes, and nothing outside the boundary is touched. The harness prompt says that a command stops after 10 minutes, that background work stops with the turn, and that long suites run in parts.
  - **Stopping during a helper showed a failure.** "Agent Watch main agent: the request could not be sent" appeared because the provider reports a request cut by the member's stop as an error ("This operation was aborted"). A request whose signal was aborted now ends `aborted`, and the helper step says "the subagent was stopped" (`managed-helper-cancelled`).
  - The profile-lock test counted a type declaration (`runtime/managed-failure.d.mts`, erased, never executed) as an unpinned runtime module; it now checks executable modules as its message says.
  - Tests: `managed-coding-tools` "processes a command left running are stopped, and only those"; `managed-workflow` "what a command left running in the background stops when the turn ends" and "stopping while a helper runs ends the turn as stopped…" (each fails without its fix).
- **Live, on the final build:**
  - Large repository 5/5 `clean` (above).
  - FE 5/5, BE 7/7 and billing 7/7, all `clean`; each had its plan brought up to date, and billing was also flagged "changed before the plan".
  - 17 edge cases: 13 as on the first run. P3·11 and P5·13 are now honestly `unverified` (the agent stayed in scope / did as told). P6·16 was not exercised by the first run and now is, which is how the stop bug above was found (`harness-edge-cases/README.md`).
  - Managed + WebUI + redaction node tests 777 pass (the 9 known receipt subtests fail), managed 81/81, WebUI e2e 26/26, typecheck clean.
- **The whole suite (`npm test`, 6,274 tests) is not release-ready: 235 failures.** Each failing file was run on the last commit (85f76db).
  - **217 failures in 37 files pass on that commit; the uncommitted work broke them.** Almost all come from the retirement of task contracts and workflow commands (`input-hook.ts`, `piagent-guard.ts`: no `piagent_task_*`/trace/verify tools, `activeSessionTask` always empty, no `pi.prompts`, phase tools off):
    - guard integration (83);
    - the HTTP/WebSocket production journeys, which assert the task record;
    - terminal receipts;
    - workflow catalog;
    - phase tools.

    The retirement is documented above ("Current sessions neither create task contracts nor register workflow prompt templates"), but its tests and docs were not brought along. 69 documents still teach `/workflow`, `/task`, `/fresh`, `/onboard`, and the English README still lists them while the Vietnamese one was trimmed (the translation-pairing check fails).
  - **Also introduced, by the local builds:** the `1.8.1-local.*` version is not a release (`release-identity`, `public-evaluation-exposure`: a prerelease must publish under `next`, and the version must be in the CHANGELOG).
  - **Already failing on that commit:** offline production schedule, install rollback (needs the `v1.2.17` tag locally), package distribution, two IE6 release checks; two journey files reach a 10-minute limit there.

## 2026-10-01 — Studio credentials redacted; search works past its limit in the sandbox; research measured on a large repository

Found while building and running the large-repository task (below). Installed **1.8.1-local.20261001.14**; managed + WebUI + redaction node tests 729 pass (the 9 known receipt subtests fail), WebUI e2e 26/26, typecheck clean.

- **Studio credentials were not redacted.** The shared redactor (`security/sensitive-text.js`, behind `redaction-core.js` and `sensitive-data.js`, used by the transcript, tool summaries, session titles, storage and evidence) knew sk-, ghp_, npm_, AWS… but not Agent Studio's own `as_live_` API keys, `as_device_` Agent Watch device tokens and `as_run_` run tokens (`as_<kind>_<uuid>_<43 base64url>`); they were hidden only behind a header or a key name, so `echo $KEY` showed the key in the WebUI. The full shape is now a token pattern; Studio's displayed key prefix (`as_live_` + 8 hex) and names like `has_run_tests` stay. Tests: `redaction-core` (+4; three fail without the pattern).
- **A streamed long line could carry half a credential.** The gateway sends an answer line by line, but a line past 1,024 characters went out wherever that fell; a credential arriving there was sent half-formed, too short for any pattern (sk-, ghp_, Studio…), and the rest arrived redacted or not at all. Pieces are now cut after a space (credentials hold none); a blob without a space goes out at 16 KiB. The extension adapter already held a line until its end. Test: `piagent-webui-file-progress` "never sends part of a credential…" (fails on the old flush). Pointed out by the research helper during the large-repository run.
- **Three more display paths leaked**, found by the agent and its research helper in the large-repository run and ported here: a `write`/`edit` preview split the text into rows before redaction, so a private key written to a file showed its body lines (a key is recognised only with its lines together); runtime command output was cut at 12,000 characters before redaction; the browser's live file label check knew sk- keys but not Studio's. Tests: `piagent-webui-tool-summary-redaction` (new; the key test fails on row-first redaction), `piagent-webui-runtime-command` (fails when cut first), `piagent-webui-live-conversation`.
- **`grep` failed in the company sandbox on large repositories.** When a search found more than its limit, Pi stopped ripgrep, but the sandbox let a process signal only itself: the search failed with `kill EPERM` or hung until the 30 s tool deadline. The same rule left **cancelled commands running**: a command stopped by the member or by the 10-minute deadline kept its processes (the large-repository run left two full `npm test` runs going for 8 and 18 minutes). Signals may now reach processes of the same sandbox only (nothing outside it); the boundary keeps the cancel/timeout as the reason instead of the worker's "Command aborted". Tests: `managed-coding-tools` "a search with more matches than its limit…" and "a cancelled command leaves none of its processes running" (both fail on the old profile).
- Agent Watch with process report version 2 installed (one Keychain approval by the owner); its manifest now carries `process_versions: [1, 2]` and the broker advertises `process-v2`. Studio stored the first version 2 report from the run below.

**Research measured on a large repository** (`harness-deep/piagent-big`: a copy of this repository, 2,833 files / 30 MB, with the real Studio-credential gap and two injected display paths that cut before redacting; hidden acceptance 5 tests, checked failing on the copy, failing on a pattern-only fix, passing on a reference fix). On .13:
- **5/5.** The agent planned before its first edit (5 steps), used `edit` this time, and fixed the shared pattern, both injected paths and three real ones it found (above), with its own tests checked against the old code through `git stash`.
- **Research was used** — once, early, after the agent had found the shared redactor: a 1,827-character brief (goal, scope by package, categories, "file:line table" answer, do not modify) asking for every path text takes to the WebUI and whether it is cut before redaction. The answer (16k characters) is where the streaming split, the live label and the runtime-output paths came from. Research 17 requests / 743k tokens (≈ $0.45), main 52 / 4.8M (≈ $1.70, mostly cache reads).
- It did not finish its process: the agent ran the whole suite of a 2,800-file repository several times (each hit the 10-minute command deadline, then a `nohup` run in the background), and the 45-minute run limit stopped the turn before the required review (`interrupted`, plan 0/5 marked). The cancelled runs stayed alive because of the sandbox signal rule (fixed in .14); a command the agent detaches itself (`nohup … &`) still outlives the turn — open.
- On the billing-size repository (95 KB) research was not used in either run, which was right; the new delegate guidance did not cause needless delegation.
- **Run 2 on .15** (the copy rebuilt at the pre-fix code: today's tree already carries the fixes, so `harness-deep/bigrepo-unfix.mjs` takes them out by their exact text, checked failing 4/5 again; 120-minute limit): **5/5, `clean`**, 31 minutes. The first `edit` was refused for the missing plan, then a 9-step plan ended 9/9; research once (2,589-character brief, 6 requests); 12 checks, none failed on the final code; the required review ran on the final patch (16 requests, no blocking finding). The agent ran the repository's whole suite only to compare the failing set before and after its change, and nothing it started was left running after the turn. Main 61 requests / 8.3M tokens (≈ $2.7, mostly cache reads), review ≈ $0.6, research ≈ $0.25; Studio stored the version 2 report.

## 2026-10-01 — the plan cannot be skipped through the shell; invented tools counted; research measured

Follows the deep FE/BE tasks below (the open points the owner asked to close). Piagent 1.8.1-local.20261001.11–.12; Studio and Agent Watch changes in their progress logs.

- **Plan guard.** With plan `require` on a complex task, every `write`/`edit` is refused until a plan exists (before: only the first one, so the other edits sent in the same answer went through). Calls of one model answer count once; after three refused answers edits go through and the turn is reported as changed without the plan (no dead end for a model that cannot plan). **Shell edits count too**: while the plan is missing, the code is compared after each command; a command that changed files gets a harness note on its result, the turn is marked `planSkipped`, and further edits and commands wait for the plan. Whatever path changed the code, a turn under a required plan that never had one ends with "code was changed before the required plan existed" (WebUI: "Đã sửa code khi chưa có kế hoạch").
- **Plan sent as a JSON string** (seen with the invented tool name) is read as the list (`prepareArguments`). A call to a tool that does not exist is answered by Pi before any hook, so it cannot be redirected; it is counted.
- **Process report version 2** (`plan_skipped`, `unknown_tools`), sent only when the broker advertises `process-v2`, which Agent Watch does only when Studio lists version 2 (`process_versions`). Older Watch/Studio keep version 1.
- **Research guidance.** The delegate tool now says when research pays (an unfamiliar area wider than a few batches of reading, a flow across modules, external docs; small lookups stay with the main agent) and how to brief it (goal, what to find, where to start and what is known, the answer needed as file:line facts); the research helper answers with file:line facts.

**Measured on a larger existing codebase** (`harness-deep/billing-core`: 115 files, about 95 KB of TypeScript, three bugs across modules — UTC days in the daily report, the accounting export and bank matching; payments to the newest invoice and the overpayment lost; partial refunds rounded per line so the journal does not balance — plus correct code nearby that must not change; hidden acceptance 7 tests, checked failing on the original and passing on a reference fix):

- Run 1 (.11): **7/7**, 179 s, main 12 requests / 404k tokens, review 14 / 269k, no finding. No research: the agent read about 25 core files in five batched `cat` commands — the right call for a codebase that fits in context. But it never planned and made every edit through `python3`/`cat >>` in the shell, so the required plan was bypassed and the run still reported `clean`. Fixed in .12 (above).
- Run 2 (.12): **7/7**, 241 s, main 13 / 405k, review 10 / 244k, two minor findings (state changed before the posting that could fail; repeated refunds split by the original line values). Its first `python3` edit got the harness note and the very next answer wrote a 5-step plan; the unfinished step came back once ("phase: plan") and the plan ended 5/5; the final note says the code was changed before the plan existed. The agent asked for a review itself (counted once with the required one) and proved its new tests fail on the original code with `git stash`. Still no research — again the right call at this size.
- .13: a turn whose steps all passed but whose plan was skipped or left unfinished shows in the warning colour, in the timeline and in the Workspace panel (the session row now carries `planSkipped`); the outcome sent to Studio stays `clean`.

A fair research measurement needs a codebase the main agent cannot read in a few batches (for example this repository, about 7 MB of source in `packages/`); not run yet.

Tests: `managed-workflow` (+4: invented plan tool and a batch of edits all refused, JSON-string plan, v2 report counts; never planning: three refusals then through, reported; a shell edit before the plan caught, commands then wait, with and without a later plan; outside git the gate still reports it — each fails with its part removed), `piagent-webui-harness-process` (planSkipped through the projection). Managed + WebUI node tests 670 pass (the 9 known receipt subtests fail), WebUI e2e 26/26, root and WebUI typecheck clean. Installed **1.8.1-local.20261001.13**.

## 2026-10-01 — deep FE and BE tasks on the Claude-only Harness, all steps required

The owner set Studio's Harness (team VNexus, revision 4): main Sonnet 5.5 at high, research Sonnet 5 at low, review Sonnet 5 at medium; plan, verify and review all `require`, `max_fix_loops` 2. Two tasks built from scratch in the workspace (`harness-deep/`: `setup.mjs` makes the repositories, `tasks.mjs` holds the requests, `run.mjs` drives one run in the member's company store, then runs a hidden acceptance suite the agent never sees):

- **FE** (`fe-inventory`, React 19 + Vite 8 + TS 7): a 2,000-product inventory page — search folding Vietnamese marks with a 250 ms debounce, category filters, sort toggles with `aria-sort`, state in the URL, a virtualised `role="grid"` (40 px rows, 600 px view), keyboard selection with a details dialog, VND prices, "Hết hàng"; logic in `src/lib` tested with `node --test`. Hidden acceptance: 5 Playwright tests on the built app. **5/5**, outcome `clean`, 478 s, 33 own tests; main 11 requests / 304k tokens, review 6 / 144k, no finding.
- **BE** (`be-orders`, Node 24 + `node:sqlite`): migrations recorded once each in a transaction; orders with merged duplicate SKUs, `BEGIN IMMEDIATE` stock reservation, 409 with the missing items and full rollback, 400 with field errors, price snapshots, `Idempotency-Key` (same body 200, different body 422), cancel with stock return and 409/404, keyset pagination with an opaque cursor. Hidden acceptance: 7 `node --test` tests in-process. Run 1: **7/7** but `review_unavailable`; run 2: **7/7**, `clean`, 320 s; main 9 / 197k, review 4 / 112k, two minor findings (a migration filename hardcoded; the idempotent replay takes the write lock).

Roughly $0.6 per task at Studio's prices (cache reads are most of the input). The research helper was not used by either task.

Found and fixed (1.8.1-local.20261001.9–.10):
- **Review of a large patch could not run.** The patch snapshot came back through one shell output, which the bash tool cuts at ~50 KB; the BE patch was 51 KB. The snapshot is now read in parts (30,000 bytes each, up to 8, digest-checked), untracked text files reach the reviewer as text instead of base64, and the reason a review could not run (`too_large`, `not_git` — including git's "Could not access 'HEAD'" —, `limit`, `failed`) reaches the final note and the WebUI.
- **The plan was written and never touched again.** Both tasks ended with their checklist at 0/4 and 0/5 (the BE agent once called a tool name that does not exist). With plan `require`, a checklist written in the turn and left unfinished now comes back to the agent once before the turn ends ("phase: plan"); if it is still unfinished the final note says how many steps are open, and the WebUI shows both.
- **The personal dashboard stopped listing new company conversations.** The relay read the company catalog together with the company project folders; the folder list had outgrown the 4 KB control-message limit (29 projects after the edge-case runs), so every refresh failed and the last known rows stayed. `project.paths` may now answer up to 1 MB (other control answers stay at 4 KB), and a failed folder read no longer blocks the rows. Company rows are reused for at most 5 s (work run outside the company Gateway raises no event there).
- Root `npm run typecheck` is clean again (`company-failure.tsx`, no `findLast`).

BE run 3 on .10: **7/7**, `clean`, 339 s; plan **5/5**, updated by the agent itself three times, so the new plan request was not needed (the system prompt now says an unfinished checklist comes back). Main 12 requests / 324k tokens, review 6 / 182k, no finding. Observed again: Sonnet 5.5 first called a tool `rival_update_plan` that does not exist (plan sent as a JSON string), then its first `write` was refused for having no plan, and it recovered on its own.

Tests: `managed-workflow` (large patch reviewed, `not_git` reason, plan asked once — updated or ignored), `gateway-control-socket-limit` (new), `piagent-webui-harness-process` (plan phase through the projection and timeline). Managed + WebUI node tests 666 pass (the 9 known receipt subtests fail), WebUI e2e 26/26, typecheck and contracts clean. Installed **1.8.1-local.20261001.10**; the personal dashboard lists the deep-task conversations again.

## 2026-10-01 — Harness edge cases run live; sessions running elsewhere; sidebar

The owner asked for every Harness workflow case to be run for real and shown in the WebUI. 17 cases (19 turns) in six policy groups ran through the real Studio, Agent Watch broker and models, in the member's company store (one WebUI project per case): `harness-edge-cases/` in the workspace holds the cases, driver, `report.json` and a README with the matrix. P1 ran with the policy Studio sends (default, all suggested); P2–P6 had the group's workflow put into the broker's manifest by the driver because the Studio admin was not signed in (grants, requests and reports were Studio's). 174 Studio requests.

Found and fixed (Piagent 1.8.1-local.20261001.5–.8):
- A turn that stopped before its process ran (member stop, failed request, crash) lost its change: "Tiếp tục" compared against the code it started from and reported no change. Such a change now carries into the next turn (`agent-watch-baseline` entry); a turn whose process ran to its end is settled as reported, so later messages are not sent back again.
- Required verification out of fix rounds skipped a required review; the review now still runs once.
- A check that needs a database or service: the agent spent ~5 minutes trying to start PostgreSQL. The gate and prompt now ask it to say so instead of installing or starting services (30 s after).
- Company conversations run outside the Gateway (company Terminal, `piagent studio --prompt`, scripts) now hold the Gateway's session lease (`ownership/managed-terminal-lease.ts`): the WebUI shows "Đang chạy ở nơi khác", disables the composer, offers no "Tiếp tục", and re-reads the catalog every 3 s while one is open (10 s while any runs elsewhere, 30 s otherwise). A dead Terminal owner shows as needing recovery; a conversation the WebUI runs is refused to the Terminal (`managed-session-open-elsewhere`).
- Sidebar: "Đang chạy · cần chú ý" first, filter All / Company / Personal, folding project groups (remembered) that show their newest five with "Xem thêm", and the company conversation's process outcome.

Left as observed: a review can win over the member's explicit request (logging a token was removed after a blocking security finding, with an explanation); the main agent sometimes asks for a review itself even when the harness reviews (counted once on the final code, one extra grant); updating Piagent while a company conversation runs breaks that conversation's file tools (new ones are fine).

Tests: `managed-workflow` (+2: review after verification rounds run out; a stopped turn's change settled by the next — each fails with its fix removed), `piagent-webui-managed-terminal-lease`, e2e "keeps many conversations readable…" and "a conversation running in the company Terminal…". Managed + WebUI node tests 664 pass (9 known receipt subtests), WebUI e2e 26/26.

## 2026-10-01 — Harness workflow: plan, checks and review before a turn ends

Follows Studio's Harness `workflow` (agent-studio progress log, same day). The harness used to leave planning, checks and review to the model; it now has a process layer, enforced in code (`managed/workflow.mjs`).

- **Policy** from the manifest: `plan`, `verify`, `review` each `off | suggest | require`, `max_fix_loops` 0–3; absent means all `suggest` and 2 loops. Review is off when the Harness has no review role. The main agent's system prompt gains a short "Process for code changes" section built from the policy; it follows a later enrollment's Harness.
- **Checks** come from the repository: lines under `## Checks` in AGENTS.md (code block or `` `command` `` bullets), else package.json scripts (typecheck, lint, test with the lockfile's runner), go.mod, Package.swift, Cargo.toml, pytest. A bash command counts as a check when it contains a declared command or runs a known test/type/lint/build runner; `echo`, `grep`, `git`… mentioning one do not.
- **`update_plan`**: the agent's checklist (1–30 steps, one in progress), stored as a session entry, shown in the Workspace panel, kept word for word through a summary while unfinished. Plan `require`: the first `write`/`edit` of a complex task without a plan is refused once (`managed-plan-required`).
- **Completion gate** after each turn of a run: whether code changed (patch digest at run start vs now; edits when the folder is not a git repository), whether a check passed on exactly the final code (the digest after the check), whether a review covers the final patch. `require` sends the agent back with a harness message (run checks; or the blocking findings) up to `max_fix_loops` times, and runs the review itself. Every code-changing turn ends with a status note. `suggest` never adds a model turn.
- **Reviewer** now reads the code around the patch (`read`, `grep`, `find`, `ls`; no write, command or web), is told the member's request and the plan, reports only correctness/regression/security/missing-requirement findings graded blocking/major/minor, ending with JSON; only `blocking` sends the agent back. A review taken before a later change stays stale as before.
- **Process report**: closing a run sends counts and the outcome (`no_change`, `interrupted`, `blocking_open`, `unverified`, `review_unavailable`, `unreviewed`, `clean`) when the broker advertises `broker_features: ["process"]` (Agent Watch from this change); a broker or Studio that refuses it still gets the plain close.
- **WebUI**: Workspace panel "Kế hoạch · n/m" checklist and the last code-changing turn's result; in the timeline, the harness's requests ("chưa có lệnh check nào chạy qua…", review findings with severity, file:line) as steps, the earlier answer kept as a step, and how the turn ended after the final answer. `update_plan` shows as a "Kế hoạch" tool with progress.

Tests: `tests/managed-workflow.test.mjs` (policy/check parsing, isCheckCommand, findings parsing, plan guard; through a Studio fixture with a real git project: required check and review loop until clean — 3 checks, 2 reviews, 2 loops, report exact; suggest adds no model turn; a required check that never passes ends `unverified` after its loop; complex task refused its first edit then planned; brokers without the feature or refusing the report still close). Mutations (check always passing, no plan guard, no gate, blocking ignored) each fail it. `managed-compaction` (unfinished plan kept verbatim), `piagent-webui-harness-process` (transcript projection with redacted findings, timeline order, session row), e2e "shows the agent's plan…" at 1440 and 390 px. `managed-helpers` now expects the reviewer's read-only tools. Suites: managed + WebUI node tests 662 pass (the 9 known receipt subtests fail), WebUI e2e 24/24, WebUI typecheck clean. Full `tests/*.test.mjs`: other failures are outside managed/WebUI (guard extension edits in progress, release version checks of the local prerelease, workflow catalog, benchmarks) and unchanged by this work. Installed **1.8.1-local.20261001.4**; live through the real Studio (default policy, all suggested): `piagent studio --prompt` in a scratch repository with a declared check — the agent changed the file, ran `sh check.sh`, asked the reviewer itself, and Studio stored the run's report (`clean`, 1 check, 1 review, matching one review grant).

## 2026-10-01 — levels per role, helpers again, GPT-6.1 Sol, API-key vendor models

Follows Studio's changes of the same day (agent-studio progress log).

- **Levels per role.** Each run carries Studio's level; the main agent switches to it at run start (a Harness may fix it), and a helper runs at its own grant's level instead of the main agent's. The level asked for at run start is the member's, or the nearest one the current Harness offers, and a harness model with fewer levels takes the nearest one instead of failing the run (before: `managed-thinking-level-unavailable` when the new main model lacked the member's level). Levels offered to the member ignore helpers whose level the Harness fixes.
- **Helpers again.** A helper role may be called again in the same message (a second review after a fix), one call per role at a time, up to 8 per role per message; the ninth is answered locally (`managed-helper-limit`) without asking Studio.
- **GPT-6.1 Sol**, newer than Pi 0.87.1's catalog, has a reviewed entry (272k, 128k output, low–max, prices) like Sonnet 5.5's.
- **API-key vendor models** (DeepSeek, Kimi, GLM, MiMo, Qwen, OpenCode, Grok) from a company key: the grant names the vendor (Pi's own provider id), the model comes from Pi's catalog and runs as OpenAI Chat Completions at Studio's `/openai/v1`, with the vendor's own thinking parameters, capped at the key's output limit. A model Pi drives over another API (Grok) runs without thinking parameters. Web search in a vendor role is answered locally (`managed-web-search-unavailable`: Claude or Codex only); `web_fetch` works. Tested with a Studio fixture continuing one conversation Claude → DeepSeek → Grok; no live vendor key yet, and the Agent Watch build that passes vendor models is not installed.

Tests: `tests/managed-failures.test.mjs` — levels from the grant (main fixed high, helpers low, member picked medium; each change undone once fails it), GPT-6.1 Sol through a harness change (removing the catalog entry fails it), helper called 8 times then refused locally, vendor models over Chat Completions. `session.mjs` moved its summary code to `managed/compaction.mjs` for the 500-line budget. Suites: managed and WebUI node tests 654 pass (the 8 known receipt failures remain), e2e 23/23. Installed **1.8.1-local.20261001.3**; live, two members on it through the real Studio, all requests settled.

## 2026-10-01 — one store per member, continuing long tasks, twenty simulated members

Asked by the owner: conversations must live on the machine once, not per key; a task stopped by a limit, a key change, a crash or a restart must continue; long tasks must not outgrow the model; daily coding tools must work on a fresh Mac; show tokens per second.

- One conversation store per member per Studio. Agent Watch still keeps one slot per key; every slot of the same member now points at one store (`<slot>/store`, `<slot>/member` under `ManagedSessions`), chosen as the slot that already holds the member's history. Slots from before are adopted by the member recorded in their conversations; nothing is moved. A conversation belongs to its member, not to a key: the member's next key lists and continues it (requests are metered to the key in use, the change is recorded in the transcript); another member's key, or another Studio, opens it read-only with the reason. Verified on the owner's Mac: the new key "Harness GPT" lists all 32 conversations of the earlier key. This supersedes the key-change paragraph of the 30 September entry below.
- Continue after any stop. A failed or interrupted turn ends with its reason and a "Tiếp tục" button (the last turn only, when idle); it sends a new message with the whole conversation, so a limit that was lifted (quota, provider limit, trial ceiling, key re-imported, harness changed) continues the task. A turn without an answer (the Mac slept or restarted, the Gateway was killed) says "Lượt này chưa có câu trả lời…" with the same button; the dead Gateway's lease is released when its process is provably gone (pid gone, or reused by a process started later), so no "Khởi động lại phiên" is needed. Conversations are files on disk: after a restart the member opens Agent Watch, then Terminal or WebUI, and continues.
- Long tasks. Pi summarises the older part when the context nears the model's window (threshold 16k below it, last 20k tokens kept); in company sessions the summary request runs inside the turn's run, with its grant. Two gaps were closed: a history longer than the window of the model a run now uses (the harness moved from a 1M-token model to a 272k one, or Auto fell back) is summarised in parts that fit, each updating the summary of the parts before, instead of one request the model refuses; after a request the model refused (`upstream_request_rejected`), continuing summarises first when the conversation is more than half the window, which covers text Pi's four-characters-per-token estimate undercounts (Vietnamese, code); and Studio's own limit of 4 MiB per request body (413 `request_too_large`, now classified as "request rejected"), which a JSON-heavy conversation on a 1M-token model reaches near 840k tokens, before Pi's threshold, is followed on continue by a summary as well. "Yêu cầu bị từ chối" now offers "Tiếp tục". While a summary runs, the live turn shows "Tóm tắt phần cũ của cuộc trò chuyện" instead of silence.
- Subagents: Studio gives each helper role once per run (one user message). A second call of the same role is now answered at once (`managed-helper-used`, kind tool) instead of three refused requests (`managed-broker:identityChanged`); the delegate tool says so. Studio also stopped counting an ended helper with unsettled usage against the key's helper limit (agent-studio progress log).
- `piagent studio --doctor`: 18 checks with the real sandbox and broker, no model request — Pi host version, search tools, sandbox, the six file tools, Vietnamese paths, outside-project refusal, no network, git and git identity, node, npm, python3, a C compiler, make, web_fetch, the key's harness and its models, Studio reachable. Required ones set the exit code.
- Tokens per second: a finished turn's footer shows `N tok/s` (output tokens over generation time, only with at least 50 tokens and 250 ms); the running turn shows a live estimate from streamed text.

Simulation (owner-approved, ceiling raised to 1462): twenty scripted members, one request at a time, through the real Studio, Agent Watch broker and harness — scouting, research with web search, plans, implementation with tests, refactors, reviews, Vietnamese paths, a stopped turn, a second turn in the same conversation — levels 1 to 5. 32 turns, all tasks correct by their own checks; Studio recorded 158 requests (153 main including 2 web searches, 3 research, 2 review), 157 settled and 1 pending (the deliberately stopped turn). Found and fixed: the repeated-helper failure and the helper-slot leak above. Left as a design point: a stopped or failed-after-start request keeps its quota hold until reconciliation, since Studio cannot prove it cost nothing.

Tests: `tests/managed-store.test.mjs`, `tests/managed-doctor.test.mjs`, `tests/managed-crash-continue.test.mjs`, `tests/managed-compaction.test.mjs` (summary inside the run; history longer than the new model's window summarised in parts; refusal followed by a summary; undercounted dense text and a body over 4 MiB summarised on continue — each change was removed once to confirm its test fails), additions to `tests/managed-failures.test.mjs` (member's next key, another member, warm conversation following Agent Watch, helper once per message, tool-loop failure continued on both protocols), the lease reuse case, the summary step in `tests/piagent-webui-file-progress.test.mjs` and its status line. Suites: managed and WebUI node tests 650 pass (the 8 known terminal-receipt failures of the unfinished receipt work remain; the subagent-failure browser test timed out once under full parallel load and passes alone and in the full run), WebUI e2e 23/23. Installed **1.8.1-local.20261001.1**; Agent Watch's binding still matches, the dashboard and the company Gateway restarted on it. Live on it: the research-subagent and review-loop members again — 24 requests, all settled; the second review of a turn was answered locally (`managed-helper-used`) and the task still finished with passing tests and a clean commit. Summaries were exercised by fixtures only (a live one needs a conversation near 255k tokens).

## 2026-09-30 — company failures say who failed and why; key or harness changed mid-conversation

Found live in the company WebUI: a turn failed with "Model đang bị giới hạn lượt gọi…" (Studio: 429 `upstream_rate_limited`), and after the team Harness main model was changed the next turn ran six requests and then failed with "Model hoặc máy chủ trung gian đang không phục vụ yêu cầu này" (Studio: 503 `connector_unavailable`; the real cause was the connector's used-up live-test ceiling, see agent-studio's progress log). The reason was guessed from the error text, the footer showed the Harness model id, and a failure to start a run left no trace in the conversation.

- One classifier (`packages/piagent-core/runtime/managed-failure.mjs`) turns Studio's code, the Agent Watch broker's error or a dead connection into a stable code, a kind and one sentence: `Agent Watch main agent: <what failed> [code] (request <Studio id>)`. The Terminal shows that sentence; a subagent's failure reaches the main agent as `managed-helper-failed: Agent Watch research subagent: … [code]`. Kinds: token quota of the key, company model account limited, account must sign in again, busy, Studio's live-test ceiling, key unusable, not allowed, new conversation needed, request rejected, no connection to Studio, model service error, configuration changed, Piagent update needed.
- WebUI: a failed company turn shows "Main agent lỗi · <loại lỗi>", what to do, and the code with Studio's request id for an administrator; a failed subagent or company web search shows the same on its step ("Subagent nghiên cứu lỗi · …"). The Harness model id no longer reaches the browser for company sessions (turn footer, errors); personal sessions are unchanged. A failure that never reached Studio is not counted as a request.
- A run that cannot start (Studio unreachable, key revoked, Harness refused) is now a turn: the message is kept and answered with the reason. Before, the send failed and nothing was saved.
- Same key, Harness changed: each turn asks Studio for a run, so the next turn uses the new main model; Pi replays the earlier turns to it (another model's thinking becomes text, tool-call ids are normalised). Verified Claude → Codex → Claude. When the new model was not among the key's models at the time the conversation enrolled, it now enrolls again (new broker, current Harness) and runs, instead of failing with `managed-grant-invalid`. Agent Watch's binding revision triggers the same before a run. A turn that is already running keeps the models its run started with (Studio fixes them per run); if the change took that model away from the key, Studio may refuse the turn's next request (`policy_denied`, shown as "Không được phép… nếu Harness vừa đổi, gửi lại"), and the next message uses the new Harness.
- Another key imported in Agent Watch: Agent Watch keeps one slot per key, and each slot has its own conversation store and company Gateway (`ManagedSessions/<slot>`). Seen live: after "Harness GPT" replaced "Vtam 3", the company WebUI listed only the new key's conversations; the 32 of the previous key stayed on disk under its slot and return when that key is imported again. New conversations run on the new key's Harness and are metered to it. Nothing mixes keys, but the list looks empty after a key change; showing the other keys' conversations read-only is an open product question. Inside one store, a transcript whose recorded key differs from the enrolled one (a safety net: it cannot normally happen) opens and reads, and answers every message with "Cuộc trò chuyện này được tạo bằng một key công ty khác…" without starting a run; before, it could not be opened at all (`session-runtime-open-failed`). A run Studio no longer recovers after a restart is left behind instead of blocking the conversation.

Tests: `tests/managed-failures.test.mjs` (classifier on the two live error texts and older raw ones; failed request answered in words and the next message works; run that cannot start; Harness changed to an unknown model; Claude/Codex/Claude on one conversation; another key at reopen and while warm, then the first key again; subagent failure reason), projection and timeline tests for the failure detail and the hidden model id, and two browser tests in `tests/managed-inference-error-web.test.mjs` (main agent and subagent failure). Suites: WebUI and managed node tests 635 pass (the 9 known terminal-receipt failures remain), WebUI e2e 23/23; each session change was removed once to confirm its test fails. Installed **1.8.1-local.20260930.19**; Agent Watch re-pinned the new entrypoint and the company Gateway started on it with the real broker. Live, after the owner raised the ceiling (462) and Studio was redeployed: Agent Watch's WebUI command on the new key, an edit-and-test turn, a second turn and a second conversation; footers read 5 + 2 + 2 requests without a model name and Studio's ledger has the same 9, all settled. The real failure paths (rate limit, ceiling, key change) are covered by fixtures only. Open: the broker reports a refused run start only by HTTP status (so a Harness refusal reads "configuration changed"); passing Studio's code through needs an Agent Watch change.

## 2026-09-30 — WebUI rebuilt as a coding-agent workspace

Approved layout (three panes) for both the personal dashboard and the company WebUI:
- Left: conversations by project, a status dot from live state (running, needs approval, needs recovery, last turn failed), "Công ty" for company sessions.
- Center: the agent timeline. Each turn shows the user's message, one card per tool call (read with lines, edit/write with a coloured diff and +/− counts, commands, searches, web search/fetch, subagents, network commands) that opens to its result, then the answer and the turn's requests, tokens, time and model. The running turn shows tools as they start and the answer as it streams. The header status comes from live state first ("Đang chạy", "Sẵn sàng", "Lượt cuối lỗi"…), and an unknown thinking level reads "—" instead of "Tắt".
- Right (Workspace, toggled and remembered; a drawer below 1200 px): uncommitted files with +/− and a link to the full review, context usage, subagents.

Server: `transcript-v1` gains optional `summary`/`change`/`result` per tool call and `usage`/`model` per answer (schema-bounded). `server/tool-call-summary.ts` builds them: paths inside the project are relative, anything else shows only its file name, commands show the project as `.` and home as `~`, every string is ANSI-stripped and redacted, results are previews of at most 60 lines/4,000 characters, and a helper's (subagent) answer is never included. The tool-result items themselves stay without content, as before.

Tests: projection (targets, diff counts, redaction, no outside paths, no helper text, usage, a multi-line command named by its first line), `tests/piagent-webui-timeline-view-model.test.mjs`, schema validation, WebUI e2e 23/23. Visual check with copies of real company sessions at 390, 820, 1280 (dark), 1440, 1920 and 2560 px: no horizontal overflow, no console errors.

Live, through Agent Watch's WebUI command and the real Studio (three rounds, a fresh git repository each): an edit-and-test turn, a re-run turn, a second conversation, then back to the first. Each turn's footer matched Studio's ledger (3 + 2 + 2 requests; all 7 settled with confirmed usage), the Workspace panel listed the two changed files with the same +/− as `git diff`, and the phone width had no overflow. Fixed on the way: a company WebUI logged a failed `/api/v1/managed` load on every new chat (a Gateway without company sessions now answers `null`); a heredoc command filled its card header (now its first line, the whole bounded command in the body); a running conversation read "Vừa xong" in the list and the Workspace panel loaded forever before the first turn ended; some copy still addressed the reader ("anh"). The subagent count stays in the header for company sessions (hidden below 600 px; also in the Workspace panel). Installed **1.8.1-local.20260930.17**. Known gap: tool cards of a running turn show the file name only; the project-relative path and line range appear once the turn is saved.

## 2026-09-30 — Agent Watch Terminal/WebUI buttons and the WebUI under real use

Driven live through the real Studio (about 22 short turns over 13 conversations: Watch's Terminal command in a pty, Watch's WebUI command, the personal dashboard), plus the Studio fix in agent-studio's progress log (failed requests held the key's two execution slots, so every turn answered `concurrency_limit` after ~30 s of client retries). Found and fixed here:
- WebUI button: failed with `managed-launch-failed` whenever the dashboard (or an earlier launch) already ran the key's company Gateway. It now reuses a Gateway of the same release (registers the project, opens a new browser session and exits) and replaces an older one. Node's experimental notices no longer fill the Terminal window. `--no-open` prints the link without opening a browser.
- The WebUI opened for a project now starts a new conversation in that project (the launch link carries it); before, it defaulted to the most recently used project.
- Terminal button: Pi's "Update Available … Run pi update" appeared (updating Pi breaks the Watch import) and the first Enter could be lost while it rendered; the company terminal now runs with Pi's startup network checks off (version, packages, catalogs, install telemetry).
- WebUI: every live event refetched the session catalog and live state; a few busy conversations exceeded 120 requests/min and got 429 (project menu empty, reads failing). Refreshes are coalesced (one in flight, one queued) and an authenticated browser session may make 600 requests/min (unauthenticated clients keep 120).
- Dashboard: after a failed start (binding refreshed by Watch a minute later) company conversations stayed hidden with stale "import again" advice until a click. Reads and a 30 s timer now re-attach to a running company Gateway (never launching it, so no Keychain prompt). Folders of company conversations are offered as projects for new chats.
- Copy no longer addresses the reader as "Anh".

Tests: `tests/managed-watch-launcher.test.mjs` (the WebUI button reuses a running Gateway and the page opens on the project), `tests/managed-company-relay-reattach.test.mjs`, `tests/piagent-webui-coalesced-refresh.test.mjs`, a loopback rate test, and a company-only folder in `tests/managed-dashboard-relay.test.mjs`; each fails with its fix removed. Remaining WebUI gaps (tool activity not shown, stale header state right after a turn such as "Đang chạy"/thinking "Tắt", no per-turn usage) are left for the WebUI redesign. Installed **1.8.1-local.20260930.14**.

## 2026-09-30 — live check through the real Studio

After Agent Watch re-imported the binding, four company turns ran through the local Studio in a fresh repository: `web_search` answered with a cited nodejs.org source; a coding task added a function and tests, ran `npm test` (3 passing) and committed with the member's own git identity; a research helper answered with typescriptlang.org sources (185 s). `web_fetch` failed on every real page with "Invalid IP address": the connection pinned to the checked address answered Node's lookup with one address, while Node (automatic address-family selection, default since Node 20) asks for all addresses at once; the fixtures had never taken that path. Fixed (`pinnedLookup` answers both forms), covered by a test that connects with and without automatic family selection (fails with the old lookup), and the same nodejs.org page now loads (status 200).

## 2026-09-30 — same result on every member Mac (machine-difference audit)

Audited what differs between member Macs and checked each case with the real sandbox (no model). Fine as it was: project folders with spaces, quotes, `$`, backslashes and Vietnamese names (decomposed on disk while typed composed); control sockets stay short for long user names. Fixed:
- Compilers: inside the sandbox `xcrun` cannot read the Xcode license record and `xcodebuild` aborts, so `cc`/`c++`/`make` (and Rust, cgo, node-gyp builds) failed on Macs that select Xcode.app while they could work with only the Command Line Tools. The runtime now resolves the compilers and SDK outside the sandbox (Command Line Tools when installed, else the selected Xcode, any name) and passes `DEVELOPER_DIR`, `SDKROOT` and the tool folders.
- Readable like on any Mac: Rosetta (x86_64 tools or an x86_64 Node on Apple Silicon crashed), the time zone database (`date` printed UTC), `/Library/Frameworks` (python.org installers) and `/Library/Java` (JDK installers). More user toolchains: mise, asdf, nodenv, goenv, jenv, rvm, conda, .NET, Yarn, `~/.npm-global`.
- Git identity is the one git gives that project on the member's machine (`includeIf` work emails, repository settings), no longer only the global one. A repository at the home folder (dotfiles) is never opened: its `.git` was writable from a project without its own repository, so a hook could be planted there.
- Git is also found in Intel Homebrew (`/usr/local/bin/git`) and in the Xcode chosen with `xcode-select`.
- Certificates macOS trusts (a company CA, a TLS-inspecting network) are trusted by the company runtime too, as Agent Watch already does; verified that `fetch` honours the added roots (no custom root is installed on this machine).
- Where Piagent is installed: the installed `piagent` command records its entrypoint, Node and Pi host in `~/.pi/agent/piagent-runtime.json` (not from a source checkout); Agent Watch pins exactly that, and otherwise also looks in nvm, fnm, asdf, mise, Volta and custom npm prefixes (it only knew `~/.local`, `/usr/local`, `/opt/homebrew`). The dashboard refuses a company runtime of another release (`managed-launch-version-mismatch`) and replaces one started before an update.

Tests: `tests/managed-coding-tools.test.mjs` (a decomposed Vietnamese folder with quotes, `make`/`c++`, an x86_64 binary under Rosetta, local time zone; commit identity via `includeIf` and repository settings; no write into a home repository), `tests/managed-runtime-registration.test.mjs`, `tests/managed-dashboard-launch.test.mjs` (release mismatch, stale Gateway replaced). Each fix was reverted once to confirm its test fails. The test run itself also differed by machine load: in a parallel full run, sandbox tests with 20–40 s limits (each tool call starts a sandboxed worker) timed out, and a timed-out test kept its fixture server open so the run never ended (seen: one hour on `managed-helpers`). `npm test` now uses `--test-force-exit` and those tests allow 120 s. Agent Watch: `StudioInstallLocationsTests` and `testManagedImportPinsThePiagentTheMemberRuns`. Not verifiable here: another macOS version, an Intel Mac, a Mac with only Xcode, a corporate proxy (Node does not use the system proxy; not handled).

## 2026-09-30 — coding tools on any member Mac

The coding-tools slice below was first checked on the owner's machine. The behaviour now depends only on what exists on the member's Mac:
- `grep`/`find`: ripgrep/fd are taken from Homebrew (`/opt/homebrew/bin`, `/usr/local/bin`) or Pi's own folder (`~/.pi/agent/bin`); when neither exists, the runtime runs Pi's downloader once before the sandbox starts (outside it, like personal Pi). If that also fails (offline, blocked), a built-in search in the sandbox worker answers `grep`/`find` from `git ls-files` (so `.gitignore` applies) or a bounded walk, skipping files the sandbox cannot read.
- Toolchains: only user-level folders that exist on that Mac are added, read-only, with their bin folders on PATH and the variables their shims need (nvm, fnm, Volta, pnpm, Bun, Deno, Cargo/rustup, pyenv, rbenv, Go, SDKMAN, `~/.local/bin`), plus the pinned Node's install prefix for `npm`/`npx`. Credential files inside them stay denied.
- Approved network (`run_with_network`) is limited to IP destinations; Unix sockets (ssh-agent, Docker, Agent Watch) stay closed.

Managed mode is macOS-only (Seatbelt). Members still need Git (Command Line Tools, Xcode or Homebrew) and Agent Watch.

Tests: `tests/managed-coding-tools.test.mjs` adds a member without ripgrep/fd (search honours `.gitignore`, skips unreadable credentials) and a fake member home with a Cargo tool and an SSH key (tool runs, key unreadable, an approved command cannot open a Unix socket; widening the profile back to all outbound makes this test fail). Full managed/WebUI suite 85/85; `tsc`, architecture and doc checks pass. Installed **1.8.1-local.20260930.9**.

## 2026-09-30 — coding tools in company sessions

Scouted with the real sandbox and worker (no model): company sessions had 9 tools; `grep`/`find` failed outright (Pi fetches ripgrep/fd at run time and the sandbox has no network, and ripgrep also failed on any project containing `.env`/`.npmrc`), `git commit` had no identity, `/opt/homebrew/bin` was not on PATH, a project inside a larger repository could not use git, `npm install` hung ~70 s, `AGENTS.md` was not in context, and there was no web search, page reading, or approved network access.

Now, following the user's choices:
- Sandbox: installed `rg`/`fd` are copied into the sandbox home and ripgrep skips credential files; the user's git name and email (only) are passed to commits; Homebrew is on PATH ahead of the Command Line Tools; a project inside a repository reads the repository and writes its git directories; offline package managers fail fast and failures say how to ask for network.
- `run_with_network`: one shell command with internet access after the user approves that exact command (approval card in the WebUI, terminal confirm in the CLI); a separate Seatbelt profile adds outbound network and DNS/trust lookups only — credential files, SSH keys and the Keychain stay unreadable, so private registries and pushes still need a scoped operation.
- `web_search`: the provider's own search tool (Codex `web_search`, Claude `web_search_20250305`) through Studio with the current role's run token, model and effort; the cited summary and source list return to the model. Queries never go to a third-party search service.
- `web_fetch`: one public https page, read by the runtime process: GET only, no cookies or credentials, private/loopback/link-local addresses refused on every redirect, the checked address pinned for the connection, 5 MB / 20 s limits, HTML reduced to text, labelled as data.
- Research helpers can use `web_search` and `web_fetch`. Project `AGENTS.md`/`CLAUDE.md` from the project folder up to the repository root are included as project instructions (symlinks, large files and personal/global files are not read).

Tests: `tests/managed-coding-tools.test.mjs` (search in a project with `.npmrc`, commit in a monorepo package with identity, writes outside the project refused, offline network refused with the hint, approved network reaches a loopback fixture while credentials stay unreadable, read-only helpers cannot use network; project instruction scope; web page address checks including redirects, HTML text, content types; search request shape for Codex and Claude, source order, Studio refusal code), `tests/managed-web-tools.test.mjs` (a real managed turn: tools offered, AGENTS.md in the developer message, the search request with the grant's model/effort/run token, the cited result back to the model), `tests/managed-network-approval-web.test.mjs` (approval card with the exact command; nothing runs before approval; allowed command reaches the fixture and the model sees its output). Live checks through Studio: see "live check through the real Studio" above.

## 2026-09-30 — company sessions inside the personal dashboard

Replaces the earlier "open the company session in its own tab" entry below. The user found a second port and a UI without history confusing, and chose one dashboard with two runtimes.

`piagent dashboard` now lists and runs company sessions in its own page. The company runtime is still a separate process: `piagent-studio.mjs --serve` starts the company Gateway without a browser or project of its own (startup probe in a private empty folder); it alone holds the Studio grants and never loads personal extensions. The dashboard's `CompanyRelay` (`gateway/company-relay.ts`) authenticates to it like a browser (one-time launch capability from its control socket, cookie, CSRF), subscribes to its WebSocket events and re-publishes them in the dashboard's single ordered event stream, and relays everything addressed to a company session: `sessions.command` (catalog revisions translated both ways; `session.create` with the `agent-watch-auto` model ref goes to the company runtime after mapping the project folder with the new control actions `project.register` / `project.paths`), session reads, attachments, runtime commands, connections and approvals (tried locally first, since approval refs carry no session). The catalog, live state and new-chat options merge both sides; company rows keep their own refs under the dashboard's project refs, so one project shows personal and company conversations together, the company ones labelled "Công ty". The new-chat model menu has "Công ty" and "Cá nhân" groups; while the company runtime is not connected, its entry states why (config missing, Keychain, disconnected key, updated binding) and connects on selection. Trade-off accepted by the user: company content passes through the dashboard process, where personal extensions run; grants do not. The relay is opt-in (`company` option) and the dashboard passes the Agent Watch binding of the Pi folder it serves, so test profiles never attach to a real company runtime.

Because `scripts/piagent-studio.mjs` changed, Watch rewrites the binding (entrypoint hash) on its next background sync or Import; until then the company entry reports `managed-launch-binding-changed`.

Reads of company sessions only attach to a running company runtime; starting it (which can ask for Keychain access) is left to dashboard start, commands and the explicit connect, so a refused Keychain prompt is not repeated by page polling. Installed **1.8.1-local.20260930.7**; the company Gateway started by the 1.8.1-…5 tab flow was stopped through its control socket (history stays on disk).

Tests: `tests/managed-dashboard-relay.test.mjs` runs a real company Gateway in its own process (fixture Studio and broker) and a real personal Gateway with the relay, then drives the dashboard in Chromium: choose agent-watch-auto, send, the answer streams in the same page (same origin, one tab), the list shows the company conversation beside the personal one, and the transcripts stay in their own folders. Also `tests/managed-dashboard-launch.test.mjs` (`--serve` start, failures by code), loopback route/relay-read test, and the hub e2e company case.

## 2026-09-30 — failed turns are shown instead of an endless wait

Live report: in a company session "hello em" produced no answer and no error. Studio had refused the turn in 0.3 s (503 `connector_unavailable`); the Gateway settled the operation as `assistant-response-failed`, but the WebUI rendered neither the live settlement error nor the durable failed assistant item, so the chat looked like it was still thinking. The client now shows a failed turn as an error notice (live, then from the transcript's `provider-*` / `assistant-output-incomplete` reason codes) until a retry in that turn answers; provider detail stays out of the browser; a later failure never hides an existing answer. Regression tests: `tests/managed-inference-error-web.test.mjs` (company WebUI against a Studio that returns 503: notice visible, raw code hidden, next message still sent) and view-model cases in `tests/piagent-webui-transcript-view-model.test.mjs`. `tests/piagent-webui-terminal-receipt.test.mjs` has 9 failures with and without this change (pre-existing). Installed **1.8.1-local.20260930.5**.

The refusal itself is Studio-side: the connector's native-bounds guard rejects any outbound `fallbacks`, and the bundled CLIProxy cloak adds `fallbacks` to every `claude-opus-5-5` (→ `claude-opus-4-8`) and `claude-fable-5-1` (→ `claude-opus-5`) request, so those two models cannot run through Studio today. See Studio `docs/implementation-progress.md`.

## 2026-09-30 — company entry in the personal dashboard

`piagent dashboard` now offers **Công ty · agent-watch-auto** in the new-chat model menu once Agent Watch has imported the Piagent configuration (`~/.pi/agent/agent-watch-managed.json`). Choosing it and pressing the send button hands the selected project to the same pinned launcher Watch uses: broker hash check, foreground `managed-authorize` (macOS may ask for Keychain access), then `piagent-studio.mjs --config … --project … --web` as a separate process with a clean environment. That process owns its Gateway and opens its own tab; the personal dashboard never holds grants. A running company Gateway is reused (a fresh tab is issued). The optional first message is copied to the clipboard, because it cannot cross into the other Gateway. The launch log carrying the one-time URL is private (0600) and deleted as soon as the URL is reported. Failures map to `managed-config-missing`, `managed-keychain-approval-required`, `managed-launch-binding-changed` (re-import in Watch after updating Watch or Piagent) and `managed-launch-timeout`. The managed Gateway itself does not expose this route.

Tests: `tests/managed-dashboard-launch.test.mjs` (binding validation, launch, log removal, Keychain denial, tampered broker, stale binding, missing import), a loopback route test (authentication, same origin, CSRF, strict body, reason codes without paths), and a Playwright case in `tests/piagent-webui-session-hub.e2e.mjs` (menu entry, optional message, Keychain error, success notice, clipboard, no personal session created). Two older cases in that e2e file still look for the removed workflow selector ("Workflow cho tin nhắn này", "Thực hiện task") and fail independently of this change.

A binding whose Watch slot was disconnected is reported as `managed-profile-disconnected` (broker exit 67) rather than as a Keychain refusal. Installed **1.8.1-local.20260930.4** (previously .3) into `~/.local` (2,010 packaged files; `scripts/piagent-studio.mjs` unchanged, so Watch's entrypoint hash stays valid) and restarted the dashboard; the live dashboard reports the company entry as available.

## 2026-09-30 — audit corrections and live verification

Installed local version: **1.8.1-local.20260930.2** at `~/.local/lib/node_modules/@piagent/platform` (2,009 packaged files matched source at installation). Doctor now inventories real launcher paths without executing old binaries and warns about multiple installations, even when version strings match. Verified three installations; the two old 1.8.0 copies are retained and lack managed mode. Working Node remains pinned to `/usr/local/bin/node`.

Fixed: unaccented Vietnamese task hints and deadlock/concurrency classification; explicit sandbox/OS permission diagnostics; public `agent-watch-auto` projection; current-session helper count (0–2, unknown when disconnected); thinking choices narrowed by Studio capabilities and every configured role. Native model context/output metadata is not replaced with an artificial shared context. A real browser fixture verified public Auto label, helper 0/2, exactly three allowed thinking choices, and scoped Git-fetch denial without an external fetch. Worker isolation tests and native helper lifecycle/CLI tests passed. TypeScript and architecture checks passed (667 source files).

Classification is still a deterministic hint, not an intelligent task classifier. It is not an authorization boundary: server model grants and budgets are. Granting several Auto candidates permits using the strongest eligible candidate; fixed mode constrains this. The helper counter describes this session; Studio owns the cross-session member limit.

Real installed Studio → Watch → Piagent acceptance after the capacity fix:

- Claude Sonnet 5.5 main → GPT-6 Luna research + Claude review → final main: 4 requests, 8,204 tokens, all usage confirmed.
- GPT-6 Sol main → Claude Sonnet 5.5 research + Codex review → final main: 4 requests, 4,384 tokens, all usage confirmed.
- Each scenario used one server run, denied root-key inference, verified team/role/model usage, and cleaned disposable credentials/members.

User added 100 requests; 8 consumed and 92 remain (persistent 70/162). These explicit delegation tasks close the previously pending mixed-provider live check; they do not measure general autonomous coding quality. No additional provider calls are needed for the corrected UI assertions.

Still open: Developer ID/update continuity and the historical full-suite failures. The old 246-failure snapshot must not be described as entirely pre-existing; workflow expectations and genuine regressions must be separated. Direct third-party CLI acceptance is separate from the Pi SDK transport tested here.

## What runs locally

- Studio migration 22: separate managed/direct keys, companion keys sharing quota, revisioned team/key harness, device/run/role tokens, revocation, epoch and lease fencing. Managed enrollment/device keys cannot call inference directly.
- Watch: independent credential slots, pinned Node and Pi entrypoint, one-click managed import, foreground Keychain authorization, background synchronization. The installed app reports `login_item_enabled`.
- Piagent: `piagent studio` and `piagent studio --web`, `agent-watch-auto`, user-selected thinking and native model context. Freeform prompts preserve original text. Current sessions neither create task contracts nor register workflow prompt templates.
- At most two read-only helper roles; no nested helpers or writing helpers. Token estimates and independence estimates do not decide admission. Each helper reports its own tokens.
- File/shell tools execute in a macOS Seatbelt worker. Managed resource loading excludes personal/project extensions, packages, trust and SYSTEM overrides. No unsandboxed fallback.
- Reviews use a working-tree snapshot including untracked files without changing HEAD/index. A later edit, including after review completes, makes the review stale.
- Scoped `fetch_origin` supports GitHub origin fetch after a visible one-operation decision. Credentials stay outside the tool worker. Arbitrary network shell and private package installation are not enabled by this approval.
- Studio waits up to 30 seconds for managed account/role capacity before dispatch. It rechecks authority and reconciles sealed usage; cancellation, revocation and unknown usage never trigger replay or time-based refunds.

## Evidence and limits

| Check | Evidence | Meaning |
|---|---|---|
| Watch Core | 329 tests, 2 skipped, no failure at the integration baseline; final managed/import checks passed | Local Swift behavior, not stable signing across future updates |
| Watch installed app | Actual import into Keychain, foreground authorization and installed Pi launcher preflight passed | Metadata-only; no provider request in this check |
| Actual Claude main | 1 request, 2,864 confirmed tokens | Real OAuth account, managed broker, native stream and ledger |
| Actual Codex main | 1 request, 1,074 confirmed tokens | Real OAuth account, managed broker, native stream and ledger |
| Actual role probe | Codex research: 595 tokens; Claude review: 726 tokens, same run | Both real helper roles work; this probe explicitly invokes the two roles |
| Main chooses both helpers, live | Did not pass: main and research ran, review was not dispatched | Capacity/settlement fix followed; no live allowance remained to repeat |
| Main → two helpers → final main | Passed with actual Swift broker, Pi SDK, Studio HTTP and PostgreSQL; four requests/60 fixture tokens | Provider is synthetic; verifies the production role/ledger path and capacity fix |
| Native CLI/WebUI | Six cross-repository provider/surface cases passed; interactive PTY startup/quit sends zero requests | Provider is synthetic; actual SDK and sandbox |
| Review changes | Both during-review and after-review mutations invalidate the receipt | Actual Git snapshot and sandbox worker |
| Scoped Git decision | Browser denial resumes chat without fetch; operation unit checks passed | Does not qualify every authenticated Git/npm/gh operation |
| Docker | Schema 19→22 rollout, backup/restore with epoch invalidation, current stack health passed | Local stack; no external server deployment |

The persistent live budget is **62/62 used, zero remaining**. No further provider call or budget increase was made. The local acceptance runner refuses inference when allowance is exhausted.

The broad historical Pi suite ran 6,147 tests: 5,589 passed, 246 failed and 312 skipped at that checkpoint. Some failures directly expect retired workflow/task tools; others exposed real PATH, architecture and installation-doctor issues subsequently fixed in focused reruns. This is **not a full-suite pass**. Historical assertions and release qualification remain separate from the passing managed acceptance tests.

## Use after import

1. In Studio, open the team or managed key's **Khung agent** and select main, research and review from that team's available models.
2. For an existing direct key, use **Thêm key Piagent công ty** to create a companion without resetting its shared quota. A direct key does not enforce the managed harness.
3. In Watch → Studio, enter the inference URL and managed key, select Piagent and Import. Keep the direct CLI configuration in its own direct slot.
4. Open **Piagent công ty**, or run `piagent studio` from the project. Add `--web` for WebUI. Choose thinking and send ordinary text.

The current local inference URL is `http://127.0.0.1:17922`; administration is `http://127.0.0.1:17920`. These loopback URLs refer to the machine running Studio. Remote member machines need the deployment's inference URL.

## Open acceptance gates

- Repeat live main-led mixed-provider delegation in both directions after a new explicit request allowance. Natural short-task delegation quality also needs a measured provider eval; deterministic routing tests do not prove that rate.
- The production-v3 benchmark grader and the independent-verification plan binding still score task-contract evidence and `/workflow` dispatch; they need a freeform redesign before the next benchmark claim.
- WebUI hunk stage/unstage/revert requires an active task and is therefore unavailable since 1.9.0.
- Expand credential-backed operations beyond the qualified GitHub fetch. Private npm, arbitrary gh and SSH-agent operations remain denied in managed tools.
- Developer ID and stable Keychain continuity across updates are not available. Local ad-hoc operation is verified; silent signed distribution is not.
- Real laptop sleep/network disruption and member pilot are not claimed from synthetic lease/crash tests. External deployment remains with the owner.

No completion or production release claim is made for these open gates. Root keys, device tokens, raw prompts and company code are excluded from evidence logs.


2026-09-30 follow-up: "Subagent x/2" header wording, sandbox note only on failed tool calls, WebUI client rebuilt; installed to `~/.local` (dashboard restarted); stale 1.8.0 copies in `~/.pi/npm-global` and `/opt/homebrew` removed. Managed/freeform tests 24/24.
