Today was about more than writing Omoikane code. I spent a lot of time thinking about how to verify changes produced by multiple AI agents and decide when they were ready to merge. Implementations accumulate. Review reports accumulate. Changes reaching main do not necessarily accumulate at the same rate.

When I checked in the morning, there were 27 open pull requests, 18 of them drafts. One was explicitly for preserving investigation material, so merging all 27 was never the goal. Still, individual contributors were making progress while integration had become a narrow passage.

By the end of the day, the CI font setup improvement and the regular expression fix had merged. A change that avoids unnecessary initialization for rejected Workers had become a draft PR. We also finally measured the bootstrap phases involved in the long-running iframe lifetime investigation. On the coordination side, I added approval buttons to the browser UI.

This entry covers room reports through approximately 18:06 JST on November 5, 2026, together with GitHub states checked while writing. Participant handles are omitted; I refer to implementation, verification, and coordination roles instead. Yesterday’s entry covered a GC optimization candidate and our analysis tools. Today follows the verification and decision-making work that came after it.

Activity and integration are different measures of progress

The cc-sakura room contains detailed updates: preparing a fix, running tests, preserving output, and having another contributor check the results. These are useful activities, but message volume can make progress look smoother than it is.

A targeted test may pass while the full suite is unfinished. The full local suite may pass while one CI environment fails. Sometimes the failure is already familiar, but a missing report prevents us from checking the remaining conditions.

A known timeout in the macOS Gate 5 continued to affect several PRs today. It did not explain every blocked change. Other PRs retained separate local failures or depended on changes that had not merged. The red checks looked similar while their implications differed.

What I wanted to know was less about who was busy and more about the condition that would allow each change to move forward. Without a clear condition, an agent can keep investigating and discovering new issues indefinitely while older PRs remain unfinished.

We therefore prioritized finishing older CI and regular expression work. Some new ideas could be prepared in separate worktrees, but not immediately turned into more PRs. An idle contributor opening another PR does not necessarily resolve an integration queue.

Heavy verification had concentrated on one machine

Full verification had accumulated on a Linux x86 machine with substantially more memory and storage. Smaller environments had sometimes lost their unit test processes to SIGKILL before producing a final summary.

Delegating to the larger machine made sense. It also put multiple tasks in a single queue: finish the full tests, verify a normal build, then begin the next task. A fresh dependency build made that queue especially slow.

Another environment had spare CPU capacity, so I asked whether it could take on more evaluation. The answer exposed a distinction: spare CPU is not the same as readiness to run a build. That environment was Linux on ARM, with roughly 8 GiB of memory, no swap, and only about 8 GiB free in its build filesystem.

Its source-sharing filesystem had more space, but that location used VirtioFS. The build guard rejected it because of executable-permission concerns. Pointing Cargo at the larger directory was not an acceptable substitute.

CPU, memory, free storage, and filesystem behavior needed separate checks. Participant handles were not reliable hardware descriptions either. An environment sharing physical resources with a Mac could still be running Linux ARM internally.

Recovering an existing evaluation environment

The storage inventory found about 40 GiB of build cache from completed work. However, some artifacts could be useful test binaries for later investigation. Deleting the entire directory without checking could remove evidence along with expendable cache.

The contributor compressed the selected target onto a different filesystem, compared the archive against the original, and then cleaned only that target. Worktrees, branches, and logs were retained. Free space was checked in actual bytes rather than relying only on rounded display values.

After recovery, free space was around 48 GiB, still close to the launch requirement. A display saying “48G” was insufficient: even a small write by another contributor could cross the threshold. At one point a shortage of a few MiB correctly prevented a launch.

The environment eventually took the engine portion of the regular expression evaluation. The engine packages produced 2,044 passes, and the targeted regular expression run produced another 11: 2,055 passes, no failures, and six ignored tests in total. The two newly added canonicalization regressions passed too. Root-level full verification remained on the x86 machine, so independently useful work could proceed in parallel.

We had not added a machine. We had recovered enough of an existing machine to make its idle CPU useful. Its memory limit remained, so the assigned scope still had to fit that environment.

Cleaning my local Mac as well

My local Mac had also accumulated generated files. It had around 53 GiB free, with old verification targets and a generated blog preview occupying substantial space.

I kept comparison binaries and logs while removing incremental build caches, reproducible blog output, and a temporary analysis-tool build. Free space increased to about 58 GiB, a measured gain of roughly 5 GiB. Directory sizes and filesystem free-space recovery did not produce identical figures, so I used the latter to describe what had actually been recovered.

I also considered attaching an external SSD to Docker. Changing a running container’s mount configuration normally requires recreating the container, and directly sharing a Mac directory might leave the same shared-filesystem problem in place.

A restart was not possible at that point, so we did not pursue that path. Wanting more storage and being able to use it without interrupting current work were separate questions. For today, preserving necessary evidence and cleaning expendable cache was the practical option.

Measuring where bootstrap spends its time

The 80-generation iframe lifetime issue continued from yesterday. We had a GC optimization candidate, but no basis to say it resolved the macOS timeout.

Static investigation revealed more about functions and accessors created during bootstrap. A large count alone did not make them safely shareable. Public function identity, Realm-specific state, and HomeObject references imposed observable requirements.

A few small helpers were possible inlining candidates, but they were not established safe reductions. A getter or string conversion inside an argument can run user code. Expanding the argument expression several times changes behavior. Candidate counts were not a sound basis for predicting a speedup.

I approved one timing experiment before considering a larger redesign. It recorded parse, AST cloning, scope analysis, linking, bytecode generation, evaluation, and related phases.

Parent bootstrap took about 276 ms and iframe bootstrap about 291 ms:

PhaseParentiframe
Total bootstrap276.2 ms290.6 ms
Parse137.7 ms123.9 ms
AST clone19.8 ms20.0 ms
Scope analysis21.2 ms21.4 ms
Link55.3 ms63.7 ms
Bytecode generation within link49.8 ms58.0 ms
Evaluate40.4 ms60.5 ms

Bytecode generation is included in linking and must not be added to it again. The reports checked nesting and sibling intervals before calculating the remaining time.

This was one Linux run using the test profile, with GC profiling and timing instrumentation enabled. These are not release-build or macOS timings, and they do not predict an optimization’s benefit. Within this run, parse accounted for about 50% of parent bootstrap and 43% of iframe bootstrap. That was enough to make the next question more specific.

Both observed bootstraps missed their caches

We observed one parent bootstrap and one iframe bootstrap, two instances in total. Both missed the source and parsed caches, and their binding-name sets differed.

It would have been easy to misreport this as “a second iframe still misses the cache.” We did not measure repeated warm iframe creation. The evidence was limited to those two instances.

Whether that separation is necessary, and whether any part could be shared, requires another investigation. Reusing immutable templates was already an idea, but expensive compilation alone would not justify it. Fresh per-Realm state and reconstruction costs still matter.

I approved a source-only investigation into key construction, the origin of the binding-name differences, and cache ownership and lifetime. That approval did not include another large experiment or a product redesign.

There was also approximately 238 ms outside bootstrap in the iframe scenario. Measuring that remainder was another option, but we chose to understand the parse/cache behavior first. The experiment narrowed the questions rather than proving a single cause.

Measurement infrastructure can fail too

The experiment did not work on its first attempt. A missing import for json! caused compilation of the test module to fail. The actual measurement had not run.

The artifact-selection script had another defect. The root crate specifies rlib and cdylib, but the selector expected a simple lib kind. Checking actual Cargo JSON exposed that mismatch.

Multiple AI reviewers had read the code and still missed these errors. Review count is not equivalent to independence: several reviewers can share an assumption and overlook the same defect.

The source correction was one import line. The runner correction matched artifact kinds to the actual manifest. We preserved the failed run, used a new output directory, and reused the target already bound to the same worktree. The eventual history was one failed compile with no measurement, followed by one successful measurement using corrected code.

For future Rust changes, the team agreed to trial cargo check with the same relevant features and toolchain as the real run. Check does not prove successful linking or test execution, and it consumes time and disk itself. Scope and cost need measurement rather than an assumption that it is always cheap.

Python artifact selection requires a separate contract. Actual Cargo JSON can serve as a positive fixture, with negative cases for incorrect kinds, foreign manifests, and unexpected features. Compiling Rust and validating the runner’s interpretation are different tasks.

Avoiding initialization for a rejected Worker

The Dedicated Worker PR addresses a path that created a child JavaScript runtime even when policy rejected startup. When rejection is already known, we should avoid starting its expensive bootstrap.

The original ten targeted tests improved from two passes and eight failures before the fix to ten passes afterward. An additional CDP test later failed during a full run, but it failed on a same-origin setup error before reaching the intended marker assertion.

We did not immediately label that as either a product defect or a flaky test. The setup was changed so that the test could reach the behavior it was intended to check.

The team also prepared mutations that deliberately disabled cleanup, then checked that the relevant tests failed at their expected assertions. A setup exception or a timeout is not evidence that a regression test detects the intended defect. The location and meaning of the failure mattered.

The corrected version eventually completed with 6,323 passes, no failures, and 24 ignored tests. All fourteen required tests passed individually within that full run, and the normal build completed. A draft PR was created from the same verified tree. Shared Worker behavior remained outside its scope, so the broader issue stayed open.

Bringing older PRs to completion

The CI font setup PR merged today. It bounds Linux font preparation and preserves evidence when infrastructure fails. Its associated font setup issue closed as well.

The Mac checks still had problems. However, the changed Linux font setup did not execute on Mac, and the two Linux environments passed. That was a decision based on the relationship between the change and the failures, not a blanket policy of ignoring red checks.

The regular expression PR also merged. Engine and root verification were split across environments, and normal CI and Release succeeded. An ARM GUI Pointer Lock failure remained a separate problem. Describing every workflow as green would have been inaccurate.

Before the evening pause, the Gate 4 integration split PR had all ten shard artifacts audited and its actual CI aggregate returned go. The team verified 6,399 passes and checked partition coverage for missing or duplicated results. Gate 5 suites were still running then, and the PR remained open.

These distinctions matter when work resumes. Recording a completed portion allows the next session to wait for or investigate only what remains instead of repeating the entire investigation.

Nayuta gained conditional seen-worklist explanations

Nayuta reached v0.5.0. It added recognition of a restricted worklist pattern using a visited set, an analysis gap discussed the previous day.

The supported template creates local Vec and HashSet values, pops a vertex, and scans its adjacency list only when seen.insert succeeds. Under the stated assumptions, it adds an expected O(V + E) explanation, where V counts reachable vertices and E counts adjacency entries.

When callers create a fresh set for every starting point, the tool sums the work across calls instead of amortizing them together. Clearing the set, extra operations, unknown calls, and unsupported forms conservatively withhold the explanation.

Existing structural candidates such as O(N⁴) remain visible. The new output supplements them rather than lowering their degrees. A remaining warning does not mean the feature is absent; an added explanation does not prove arbitrary graph traversal code.

I checked all 38 tests and the CLI output, then distributed the release to the contributors. Bugs should go to nayuta’s issue tracker with the version, a minimal reproduction, actual output, and expected behavior. Tool distribution is part of a feedback loop rather than the end of the work.

Keeping approval requests out of the general conversation stream

I added an approval queue to the cc-sakura HTML UI today. Requests for permission had been mixed into ordinary progress reports, making unanswered decisions hard to recover later.

As the system owner rather than an implementation contributor, I do not need to participate in every review detail. I need to understand which decision is mine and what it authorizes. A shared stream made that boundary difficult to see.

Contributors now submit approval_request messages with a title, a scoped description, and options. The UI lists unanswered requests and posts the selected option as an approval_decision replying to the original message.

{
  "type": "approval_request",
  "title": "Statically investigate cache keys",
  "description": "Source reading only. No additional measurement or product changes.",
  "options": ["Approve", "Hold", "Reject"]
}

The UI does not ask an AI to infer approval requests from arbitrary prose. The requester explicitly defines the decision and its options.

It uses existing history and send APIs, so old clients can still send and receive these as ordinary JSON messages. Only the newer HTML client interprets them as an approval queue.

Decisions survive a viewer restart

Browser memory alone would lose the queue on restart. Room history is therefore the source of truth: the viewer reconstructs requests and decisions initially, then fetches updates incrementally.

Within a single viewer process, decision posts are serialized and history is checked before sending. That prevents duplicate posts from double-clicks. Strict exactly-once behavior across simultaneous separate viewer processes is not guaranteed.

Posting is enabled only with --allow-approvals; the normal invocation remains read-only. Anyone holding an enabled viewer URL can submit a decision using the configured participant’s identity, which changes how that URL should be handled.

A room decision does not change the sandbox or approval settings of Codex or Claude. Contributors were told to check the known owner’s sender and handle, the original request ID, and the selected option. A payload field named source is not identity verification.

The approval UI PR and startup and HTTP logging PR were merged separately. After updating the viewer, I checked page and approval API responses and notified the agents of the new message format. Existing prose requests do not automatically appear in the queue.

Confirming resources before expanding the queue

I also approved evaluating a second x86 environment for distributed verification. That did not authorize buying hardware or paid cloud resources. The first step was to inspect existing participants and identify concrete work they could take.

One candidate lacked storage. About 33 GB of target data was connected to evidence for previous PRs and could not simply be classified as disposable cache. The reported requirements implied roughly 68 GiB of additional capacity.

Meanwhile, the ARM environment had already demonstrated useful engine-level parallelism. Not every task needs the same machine. We need to distinguish x86-specific verification, work that can run on ARM, and source-only investigation.

Before adding resources, I want to know how much waiting comes from dependency builds, full test execution, and result reviews. Today, all three were substantial.