Architectural review · 21 Aug 2026 · origin/main @ 300bea1 · v0.2.105

The Chrome Agent Platform is healthy. Its delivery process is not.

The code builds, 632 unit tests and 126 real-browser journeys pass, and the hub renders in 65 ms. Meanwhile shipping has fallen from 83 commits a day to zero, 46 branches of finished work sit unmerged, and the machinery built to guarantee correctness is now the thing preventing delivery.

Chrome journeys 126/126 Run today at 300bea1
Unit tests 633/0 green on durable disk
Commits, 20 Aug 3 was 83 on 17 Aug
Git worktrees 100 17 hold zero work
Unmerged branches 46 ahead of origin/main
Open handoffs 322 152 BLOCKED, 0 closed
/tmp inodes 100% 1,043,303 of 1,048,576
Tasks confirmed 0/31 nothing has reached done

What it's supposed to be

From PLAN · PRODUCT · CONSTITUTION

Reading PRODUCT.md, PLAN.md, README.md and docs/CONSTITUTION.md together, the intent is coherent and genuinely distinctive:

Chrome itself becomes the agent runtime. Not a chat window that happens to live in a browser — the new-tab page is replaced by a command center where you start a task, watch it run, and drill into what the agents did. Everything an agent needs is a browser primitive: tabs, alarms, OPFS, capture, the page's own JavaScript.

Four ideas carry the product:

Around that sits a design position — "Operate. The tool must disappear into the work" — warm-neutral paper, one petrol-teal accent, inline SVG icons, no decorative motion. And a delivery position: never accept "it serves" as "it works"; drive real behavior in a real browser; independent review by a different model before push.

The plan is good. It is not the problem.

What it actually is

Verified today, not read from docs

I built and drove the current public head rather than trusting the trackers. Everything in this section is something I ran.

Buildnode build.mjs succeeds. Bundles the AI SDK + zod into a 2.8 MB service worker, strips one new Function site and two probes, runs an AST scan over 72 shipped files, syncs the component gallery.
Unit633 pass, 0 fail — but only with TMPDIR pointed at durable storage. On the host's default temp filesystem the suite dies at tests/cdp-client.test.ts with "No space left on device". Same commit, same code, two results.
Browserscripts/chrome-journeys.ts: 126/126 pass against a real loaded MV3 extension — enrollment, disenroll races, management tools, owner approvals across a service-worker restart, no SW console errors, clean profile teardown.
RenderHub reaches domContentLoaded in 62 ms, load in 65 ms. Well inside the 1 s budget.
Boot postureFresh profile: zero granted permissions, zero CSP violations, one warning ("storage permission not granted — changes are session-only").

The hub renders correctly and looks like the product brief: paper ground, teal accent, SVG icons, a composer, agent groupings, artifacts, activity. agent-do really is imported from npm and bundled — lib/agent.js wraps the library rather than reimplementing it. The security model in the code is not theatre: sender authorization derives origin from the content script's own realm, page senders hit a route allowlist, tool descriptors get zod-validated, untrusted strings go through escapeHtml/escapeAttr at all 65 innerHTML sites I checked.

Bottom line on the code

This is above-average extension engineering — better reasoned than most production MV3 code I've read. The comments explain threat models and race conditions rather than restating the line below them. Whatever has gone wrong here, it is not code quality.

What it is not yet: a product anyone can pick up. On first install you get an empty hub, a red error badge, and no onboarding. Nothing works until you find Settings → Permissions and Settings → Providers yourself. The Agent Directory page ships unstyled with no navigation. The side panel's Page tab is a URL field and a status line.

Where it went off the rails

The diagnosis

You said it feels off the plan. Here is the shape of it, measured.

Commits landing on origin/main, per day
14 Aug4
15 Aug55
16 Aug70
17 Aug83
18 Aug65
19 Aug20
20 Aug3
21 Aug0
Delivery collapsed 96% in 72 hours. Nine pi sessions are running right now, seven of them continuously for more than a day. Zero Chromium processes are running, so no browser acceptance is in progress either.

The collapse starts on 19 August. That is the day bbeff7b docs(project): integrate crash-recoverable task tracker landed, and it is the day after the intercom handoff lifecycle became mandatory in your journal's CLAUDE.md.

Those two artifacts are individually reasonable. Together they created a delivery process with the following properties:

1. The gate is unreachable by construction

The lifecycle is OPEN → IN_PROGRESS → REVIEWING → REVIEW_PASSED → READY_FOR_BROWSER → INTEGRATING → GATED → PUSHED → CONFIRMED. Nine states. CONFIRMED requires explicit product-owner confirmation — you, personally, per task.

Across 31 tracked tasks: zero are CONFIRMED. Four sit at PUSHED with "Next: obtain confirmation" — some since 18 August. Because nothing can retire, the tracker only grows, and every new task inherits a dependency graph of unretired ancestors. Six of the fifteen currently-open tasks name another open task as a blocker.

2. Rebasing became the work

Read the tracker's own language. CAP-FB-20260819-AGENT-DIRECTORY-01: "READY_FOR_BROWSER only on old base; not deliverable… the accepted Directory delta must be recreated and independently checked on ffbdf28 before browser use." The same sentence appears on ARTIFACT-TX-01 and USAGE-RECORDING-01.

Work passes review, main advances, the review is voided by the base change, the work is recreated on the new base, and it needs review again. With serialized integration and one browser queue, a lane can never catch up. This is the mechanism that turned finished code into 46 permanently-stale branches.

3. Evidence became the deliverable

The handoff ledger holds 322 records. None are closed. The distribution is the tell:

Handoff stateCountWhat it means
BLOCKED152Stopped, awaiting an owner or a predecessor
FIX_REQUESTED82Review rejected it; correction owed
INTEGRATING62Queued behind the serialized browser gate
REVIEW_PASSED15Approved, not integrated
PUSHED5Shipped, unconfirmed
REVIEWING / RECEIPT_ACKED6In flight

72% of all handoffs are BLOCKED or FIX_REQUESTED. The fleet is spending its time producing versioned acceptance packages — v7, v8, v9, v10, v11, v14, v15, v16 of the same lane — that get statically reviewed and rejected before a browser ever runs. In your journal's project file the phrase "no browser result exists" appears repeatedly.

4. The tracker lost contact with the repository

This is the finding I'd act on first, because it means the tracker is actively misleading you.

Fifteen tasks read Owner: unassigned, Branch: none, Candidate: —. At least nine of them have committed implementation work sitting in branches right now:

Task (tracker says OPEN / unassigned / no branch)Work that actually exists
CONVERSATION-RUN-STATUS-01 P0feat/overnight-conversation-status-nub
COMPOSER-AGENT-MENTIONS-01 P1feat/composer-agent-mentions (+3), b4a0a6f
DURABLE-BACKGROUND-RUNS-01 P04 branches, up to 6 commits ahead
PERMISSION-REMEDIATION-UX-01 P0docs/permission-remediation-design (+5)
AGENT-DELETION-LIFECYCLE-01 P1docs/agent-deletion-lifecycle (+3)
SITE-AGENT-STATUS-CLEANUP-01fix/site-agent-copy-status-cleanup (+2)
LOCAL-MODEL-MANAGEMENT-01feat/gemma4-catalog-preflight
PAGE-SCOPED-SITE-IDENTITY-01 P175e2acf (detached, /tmp only)
DIRECTORY-TOOL-EXPLORER-01 P13 directory branches

The join key is broken at the root: the tracker exists so a stable CAP-FB-* ID links a task to its commits. 2 of 430 commits mention a CAP-FB ID. Every Recover: field in the tracker prescribes a git grep that will not find its own work.

5. Some agents are producing nothing at all

Seventeen worktrees sit at exactly origin/main with zero commits. Ten of them are /tmp/cap-page-identity-directory-prep-v3 through -v13 — identical checkouts of the same tip, no work in any of them. That is a loop: create a versioned prep directory, fail a static review, increment the version, repeat. The same shape appears in the scheduled-task lane (v14, v15, v16) and the durable lane (v9, v10, v11).

That is what your dissatisfaction with the other agent is actually measuring.

Verified defects

Reproduced, not inferred
Critical Environment Reproduced

/tmp inodes are 100% exhausted — this machine is currently degraded

df -i /tmp reports 1,043,303 of 1,048,576 inodes used. The same unit suite on the same commit is 633/0 with TMPDIR on durable storage and fails outright on the default temp filesystem — so any gate result produced on this machine right now measures its disk, not the code. It will fail any process needing a temp file, not just this project. Roughly 60 full git worktrees live in /tmp at about 7,560 inodes each, alongside 235 cap-picker-full-* and 104 cap-run-status-evidence-* retained evidence bundles.

/tmp is tmpfs — RAM-backed. A reboot destroys all 60+ worktrees and every retained evidence bundle referenced by the tracker's Gates: fields.

Critical Data loss risk Mitigated

Seven commits were reachable only from RAM-backed worktree HEADs

Seven detached worktrees in /tmp held commits not reachable from any branch. The objects live in the main repo, but the only refs pointing at them were the worktree HEADs — on reboot they become unreachable and eligible for git gc. One is b4a0a6f feat(composer): route agent mentions canonically across named, background and site agents — implementation of a task the tracker calls unstarted.

I tagged all seven as rescue/tmp-detached-* in the main repository. That is additive and reversible; nothing was deleted. Run git tag -l 'rescue/*' to see them.

P0 Architecture Code-confirmed

Runs are not durable — they live inside a message handler

The run-task route calls runTask() directly inside chrome.runtime.onMessage. There is no persisted in-flight run record; activeRuns in lib/scheduler.js is an in-memory Map. The service worker's own comment is explicit: "The port keeps the SW alive while a page is listening."

So a long agent run's survival depends on a UI page holding the agent-progress port open. Close the hub tab and the run is gone — tool side effects already committed, no result, no resume. Tool calls are journalled for replay, but the run cannot be reconstructed from them.

This is CAP-FB-20260819-DURABLE-BACKGROUND-RUNS-01, correctly rated P0, and it has four branches of work against it while the tracker lists it unassigned.

Major First run Reproduced

A fresh install looks broken and offers no way forward

New profile, zero permissions: the hub shows an empty state and a red error-console badge reading 1. The entry is honest — "storage permission not granted — changes are session-only until enabled in Settings" — but a first-time user reads a red badge as a fault.

The deeper issue is what "session-only" means under MV3. Without the optional storage permission, lib/kv.js degrades to an in-memory Map owned by the service worker. MV3 terminates idle workers after roughly 30 seconds. So a user who enters an API key without granting storage loses it almost immediately, with no warning at the point of entry. There is no onboarding flow anywhere in the extension — I grepped for one.

Major UI Reproduced

WebMCP discovery status renders outside its card

In ntp/ntp.html, .panel-body { padding: 4px 0; } — no horizontal padding, so each row supplies its own. #webmcp-hub-status carries the panel-body class but has no CSS rule anywhere in the repo, and ntp.js:113 writes straight into el.textContent. Result: "Discovery has not run yet." sits flush against the panel edge, roughly 14px left of every sibling row, breaking the card boundary.

Visible in the screenshot of a clean build of current main. Not in any tracker.

Major P0 — still live

The standalone run-status banner is still on main

ntp/ntp.js:1192 still binds document.getElementById("run-status") and sets a generic top-of-thread state. CAP-FB-20260819-CONVERSATION-RUN-STATUS-01 is P0 precisely because ffbdf28 fixed lifecycle ownership fencing and was mistaken for fixing the presentation. Worth confirming this distinction is understood before the next attempt — it is the kind of thing that gets re-closed without being fixed.

Structural Maintainability

Three files carry too much of the system

background/service-worker.js is 4,799 lines exposing 127 message routes in one flat handler object. shared/components.js is 5,193 lines with 33 custom elements. options/options.js is 1,775 lines.

This is why every change needs cross-subsystem review and why merges conflict so readily — the serialized integration queue is partly a consequence of this shape. It is also why AGENTS.md needs a rule reminding agents to grep for couplings: the couplings aren't expressible in the module structure.

Structural Incomplete rename

recipes → skills never finished

The UI says "Skills". The code ships extension/recipes/, lib/recipes.js at 655 lines, and options.js importing RECIPES — while lib/skills.js is 39 lines. ntp.js:1586 reads openView("recipes/index.html", "Skills"). Your own AGENTS.md cites this rename as the example of cross-subsystem drift; it's still drifting.

What to cut

Reduce surface before adding

Opportunities

Where the plan is under-exploited

Lead with sites-as-sub-agents

This is the genuinely novel claim and the one nothing else does. Right now it's buried behind an unstyled Directory page and a discovery status line that renders outside its card. A curated set of five real origins that expose useful WebMCP tools, reachable in two clicks from the hub, would demonstrate the entire thesis in thirty seconds. Everything else in the product is table stakes by comparison.

Make the permission model the feature, not the tax

"permissions": [] is a real differentiator against every other agent extension, and it is currently experienced as friction. The docs/permission-remediation-design.md work already specifies the right thing: a failed run names the exact tool, capability, origin, and rationale, then offers the minimal grant and resumes the same run. Shipping that turns your hardest constraint into the thing people notice first.

Semantic tool search is the right next architectural bet

CAP-FB-20260820-SEMANTIC-TOOL-SEARCH-01 is well-aimed. With 127 message routes, a management toolset, and an unbounded catalog of per-site WebMCP tools, context pressure is going to be the binding constraint long before model quality is. A local hybrid semantic + lexical top-k over the tool catalogue is genuinely load-bearing. I'd raise its priority — but only after durable runs, because it is meaningless if runs die when a tab closes.

A first-run flow

Three steps: pick a provider and paste a key, grant storage in the same gesture, run one seeded example task that produces a visible artifact. That single path converts an empty hub with a red badge into a working demo, and it is a day of work.

Split the service worker along the routes that already exist

The 127 routes cluster cleanly — agents, memory, provider, tools/WebMCP, artifacts, permissions, diagnostics. Extracting them into route modules with a thin dispatcher would let lanes touch disjoint files, which is the actual fix for the merge-conflict tax that the serialized queue is compensating for.

Worktree and branch reconciliation

You asked whether they're really in progress

Short answer: about ten distinct lanes are live. The other ninety are residue. Counts below exclude your main checkout and the temporary worktree I made for this review, leaving 98.

ClassCountDisposition
Genuinely active
HEAD commit dated 20–21 Aug
14 About ten distinct lanes once duplicate pairs collapse: durable product successor (+6), durable policy (+5), scheduled-task v3, Gemma 4 preflight, page-directory snapshot, provider-perm, docs. Keep.
Stale
HEAD commit before 20 Aug
67 Includes real reviewed work: usage-retry-fix (REVIEW_PASSED), picker-harness-cdp (+25), integrate/provider-5e5c81e (+28), assets, artifact-tx, directory. Triage: land or abandon. Do not rebase again.
Zero work
sitting at origin/main exactly
17 Ten are cap-page-identity-directory-prep-v3…v13. Delete — nothing is lost.
Redundant copies
sharing a HEAD with another worktree
27 73 distinct HEADs across 100 worktrees. e.g. cap-durable-product-successor-fa3c51c and -2998258 both sit at 2998258. Keep one each.
Orphan-risk detached
/tmp-only refs
7 Now tagged rescue/tmp-detached-*. Safe to prune the worktrees.
Also worth knowing

Your primary checkout at ~/chrome-agent-platform is on a detached HEAD at 5e5c81e, eleven commits behind origin/main. If you've been inspecting the product there, you have been looking at 19 August's build, not 20 August's. It's also missing fake-indexeddb because its node_modules is a symlink into /tmp/cap-assets-ui/ — a directory that vanishes on reboot.

What I'd do

In this order
  1. Reclaim /tmp today

    The machine is degraded now. Delete the 17 zero-work worktrees and the retained evidence bundles, then move all remaining worktrees to ~/cap-worktrees/ so a reboot stops being a data-loss event. The rescue tags mean nothing is lost either way.

  2. Restore the primary checkout

    git checkout main && git pull && npm install. Stop testing eleven commits behind.

  3. Confirm or abandon the four PUSHED tasks in one pass

    RUN-STATUS, WEBMCP, AGENT-ACCESS, SYSPROMPT are all shipped and waiting only on you. The 126/126 journey run in this review is the regression evidence they're blocked on. Closing them unblocks the three tasks that name them as dependencies.

  4. Reconcile the tracker against git before anything else is planned

    Nine "unassigned" tasks have real branches. Write those branch names and commit SHAs into their entries. Then make CAP-FB-… in the commit subject a hard rule — today it's 2 commits out of 430, which means the tracker's own recovery instructions don't work.

  5. Freeze main for a triage window

    The rebase treadmill is the single largest waste. Stop advancing main, land or abandon the ~37 stale branches against a fixed base, then reopen. Nothing else fixes the "reviewed on an old base, must be recreated" loop.

  6. Cut the lifecycle to four states

    OPEN → IN_REVIEW → MERGED → DONE. Keep the two rules that are load-bearing — a different model reviews, and a real browser drives it — and delete the rest. Nine states with content-addressed gate evidence and remote attestation has produced 322 open handoffs and zero confirmations. Optimise for one shipped increment per day, not for an unfalsifiable audit trail.

  7. Then ship durable runs, then the first-run flow

    Durable runs first because everything else is unreliable without it. First-run second because it converts the project from a thing you can inspect into a thing someone else can use. Both already have code or a design doc; neither needs new research.

The one-sentence version

The Chrome Agent Platform is a well-built, genuinely novel extension that passes every gate you set for it, and it stopped shipping because the process you built to protect its quality now costs more than the quality it protects — so the fix isn't more engineering, it's retiring the tracker, landing the 46 branches of finished work, and getting back to one visible increment a day.