I am building a browser called Omoikane. Recently, I have been connecting AI agents running on multiple machines and execution environments through a chat system called cc-sakura.
Giving each AI the same explanation, then copying its response to another AI, was tedious. One agent might be implementing a fix while another investigated the same area. Whenever a test failed, I also had to sort out whether it was a known failure or something introduced by the new change.
Why not let the agents talk directly?
That was the starting point. In practice, though, being able to exchange messages was not enough. We needed to communicate who had accepted a task, whether they were working or waiting, what they were waiting for, and which evidence supported a decision.
This article describes the workflow as of November 3, 2026. I have omitted the participating environments’ handles and refer to roles such as implementer, independent verifier, and native-machine tester. These roles change with the work and availability. The figures and progress reflect the records I checked; I did not rerun the entire test suite for this article.
I wanted to stop relaying every message
When using multiple AI agents, dividing the work seems an obvious first step. Ask one to implement and another to review, and the work should happen in parallel.
But separate conversations quickly drift apart. The implementer may be looking at a new patch while the reviewer still reads the previous one. Another environment might start the same expensive test while the implementer is already running it, slowing both down.
This can work if a human keeps track of everything. As the work grows, however, I become a necessary messenger. AI may write code faster, but that does not make me faster at reading, summarizing, and pasting between conversations.
With cc-sakura, I wanted to reduce that relay work. The agent implementing a change posts its details and verification conditions to the room. Another reads them, runs verification, and reports back in the same place. When I want an update, I ask an agent to read the room and explain the state.
I am the system owner, not someone who always takes implementation tasks. I have therefore asked participants not to direct every developer question at me. Ordinary investigation and task coordination should proceed among the agents. They should involve me when decisions concern cost, disclosure, priorities, or similar owner responsibilities.
cc-sakura starts with a shared conversation space
cc-sakura is a small, centralized chat system. Agents join a room, send messages, and read history. The CLI, HTTP API, and MCP all provide access to the same conversation.
The server is written in Rust and stores conversations in SQLite. Participants have UUID identifiers and display names. A session issued when they join authorizes operations in that room. A room is a conversation space; a session is a secret credential. They are different things.
The broad structure looks like this:
flowchart LR
A["Implementation agent"] -->|Post and read history| S["cc-sakura server"]
B["Independent verification agent"] -->|Post and read history| S
C["Native-machine testing agent"] -->|Post and read history| S
S -->|Persist| D[(SQLite)]
S -->|History and new messages| V["HTML client"]
V -->|Original text, translation, context| H["Human"]
The server does not automatically break down work or enforce assignments. Agents accept tasks and coordinate priorities in the room. We still use GitHub Issues and pull requests. The room handles detailed coordination and intermediate progress, while specifications, changes, and verification results that need lasting references go into Issues and PRs.
That distinction matters. Building a chat system does not complete a development management system. Once we could share conversations, the next question was how to use them.
Do not force AI communication into human-facing Japanese
One requirement was present from the beginning: I wanted AI agents to communicate in whichever way was most efficient.
They do not need a greeting and a long Japanese explanation for every update. If a short JSON object conveys the same information, that is fine. English, shorthand, or a shared custom format is also fine. Humans should still be able to check the meaning when necessary.
cc-sakura accepts arbitrary JSON payloads. The following is an illustrative format, not a quotation from an actual message:
{
"task": "worker-bootstrap",
"state": "waiting",
"blocked_by": "full-test",
"next": "independent-review",
"reply_to": 120
}
For repeated expressions, participants can register a dictionary called a codec. It maps short keys to a human-facing template, avoiding repeated explanations. Neither registering a dictionary nor attaching a human-readable gloss is mandatory. This policy is described in the README’s communication format section.
“Efficient” does not simply mean fewer characters per message. Cryptic shorthand is expensive if recipients misunderstand it or spend several exchanges negotiating a dictionary. Referencing an earlier message by ID, avoiding repeated conditions, and sending only what changed can be more useful.
The current room also uses natural-language explanations inside structured JSON. We have not invented a new AI language and measured a particular percentage reduction in communication. The point is to avoid prescribing one format and allow participants to choose a lower overall communication cost for the situation.
Ask a local AI client to interpret messages when a human reads them
If agents communicate freely, will humans lose the ability to read the conversation? We put interpretation on the reading side.
Rather than translating continuously on the server, the human-facing client launches Codex CLI or Claude Code on the reader’s machine. Existing dictionaries or glosses are used when available, and a model is called when necessary.
Launching a CLI locally does not mean inference stays entirely on the device. Where conversation data goes depends on that CLI’s authentication and configuration. If the content cannot be sent externally, it needs to be read in its original form without translation.
Nor can a model recover the meaning of every unknown symbol sequence. Without a mapping or sufficient context, it cannot know what was intended. Results distinguish an interpretation, an interpretation involving uncertainty, and an undecipherable message. Fluent prose is not proof that the intended meaning has been recovered.
This is also why we keep the original text. If only a human-facing summary survives, it becomes impossible to check the basis of the interpretation. Original messages, message IDs, and references remain available to trace later.
Translation alone does not explain what a conversation is about
Soon after starting to use the room, I wanted a separate “explain the background” feature.
Even a message already written in Japanese can be hard to understand: “Keeping the previous conditions; waiting for the fixed patch.” What conditions? Which patch? Who is waiting, and what will move forward when it arrives? This is a different problem from translating languages.
We added a separate button to the HTML client in PR #8. It supplies the target message, nearby conversation, and ancestors of explicit reply references to the model, asking it to explain the topic, history, ownership, and next steps.
The explanation must cite supporting message IDs. It should also distinguish someone’s report from independently checked facts. “The implementer reported success” and “another environment confirmed success under the same conditions” mean different things.
The input has message-count and size limits. It does not automatically investigate the entire room history or linked Issues. I do not treat the button as a source of the complete truth about the project. It is a foothold for returning to a conversation.
Making individual messages read naturally was not enough. What I often needed was help reconstructing the connections between them.
Declaring work before starting makes duplication visible
One early rule was to announce what you were going to do before starting. Anyone already working without having shared it should post an update then.
Tasks touching the same file are particularly likely to conflict. Reducing browser initialization work and restricting internal API exposure can affect the same large file despite having different goals. Separate Issues do not necessarily represent independent work.
A useful announcement includes the Issue or files involved, what will be done, and what is outside the current scope. For example, limiting a first stage to an inventory and test design allows investigation to proceed while postponing broad edits until their order relative to another PR is agreed.
That is how we approached Issue #1697 on internal function exposure. Inventory and regression-test design come first. Implementation overlaps with the Worker and Worklet initialization PR, so the participants need to agree on timing and where to put the changes.
The server does not acquire an exclusive lock. Participants can still miss announcements or announce work and forget about it. Even so, being able to discuss potential conflicts before editing has been useful.
“Is anyone doing this?” does not establish an owner
We also changed how we ask questions.
Posting “Is anyone working on this?” can leave a task untouched when nobody is. It is a question, but it does not specify who should answer.
Instead, I asked participants to address someone explicitly and check whether they could accept the task. cc-sakura mentions use participant UUIDs rather than matching display-name text. Although I have omitted names here, requests in the room identify their intended recipients.
Sending a request and receiving an acceptance are different events. A returned message ID proves that the server stored the message, not that the recipient accepted it. A reply such as “after my current verification finishes” or “I cannot take this” establishes the actual state.
We also avoid scheduling around an answer from someone who is inactive. If an environment has hit its token limit, its unavailability until recovery is shared. Being listed in the room does not mean an agent can currently take work.
A task can have an owner and still stop moving
Clear ownership does not solve everything.
Someone can accept a task, move into another investigation, and leave the original task stalled. They may intend to return, but everyone else sees a task with an owner and avoids touching it.
We therefore distinguish active work, waiting, and future plans. If someone is waiting, they should record what they need, the condition for resuming, and whether the work can be handed off. If they cannot get to it, the task should move rather than remain held indefinitely.
The current macOS diagnosis exposed this issue. The native-machine tester has prepared the environment, but the prerequisite diagnostic patch is not ready. The coordinator has asked the implementer to prioritize that patch over other investigations and suggested transferring it if necessary.
An ownership list and actual progress are different things. “The fixed patch has not been created yet” is more actionable than a vague “preparing.” Avoiding optimistic restatements of progress matters considerably in parallel work.
More AI agents do not remove a CI bottleneck
Writing code is only part of Omoikane development. Rust builds, full test runs, Web Platform Tests, and checks across operating systems and CPU configurations take time. Producing a fix quickly does not make it mergeable before verification finishes.
We made CI improvement itself a room topic. Instead of merely listing red checks, participants share the causes being investigated, which PRs depend on them, and who will investigate next.
Similar-looking timeouts need not have the same cause. Exceeding a JavaScript execution budget, reaching an overall job limit, and running out of memory during compilation require different investigations. A later step running does not erase an earlier failure.
We also try not to change the success criteria while adding diagnostics. Making a test green by increasing its timeout does not demonstrate that execution now fits the original budget. An observation patch and a fix that changes product behavior are handled separately.
More agents can propose more work, but they do not add execution slots, disk space, or CI time. Parallelization can simply lengthen the verification queue. We need to be able to explain which stage is actually blocking progress.
Ask an independent environment to verify the same change
Multiple environments offer more than task division. They let us check the same change elsewhere.
For PR #1694, which reduces Worker and Worklet initialization work, an independent Linux environment built the fixed commit with the normal feature configuration and ran comparisons in addition to the implementer’s measurements. Two cases were tested with three alternating pairs each, using inputs corresponding to the before and after conditions.
That environment ran Linux x86_64 on an Intel Core i3-1115G4, with CPU affinity set to four and Rust 1.98.1. The recorded median libtest times changed from 3.59 to 2.85 seconds for one case, and from 0.95 to 0.80 seconds for the other. Those results describe two specific cases, not an equivalent speedup for the entire browser.
A separate library run reported 4,933 passes, no failures, and 24 ignored tests. Meanwhile, the implementer’s environment still had a RuntimeLimit failure in a full run. We do not ignore that failure because another environment passed. Completion of later stages of the full Cargo test command and the build must also be checked separately.
For small performance differences, I prefer collecting evidence across environments and discussing practical impact rather than requiring one machine to provide a perfect causal explanation. Background work can influence measurements. This is not majority voting that cancels failures, nor a formal distributed consensus protocol. It is a way to improve the evidence available for a decision.
Make “we tested the same source” checkable afterward
One difficulty with independent verification is knowing whether everyone tested the same thing.
A branch name is insufficient. The branch might move during verification, or uncommitted local changes might be present. Features, compilers, and test profiles can also change the meaning of a comparison even at the same commit.
We record the commit, tree, hashes of relevant sources and lockfiles, commands, binaries, and logs in relation to one another. Shared evidence points to a fixed revision rather than an unspecified latest version.
Recipients do more than read the word “success.” Where possible, they check reconstructed file hashes, log counts, sets of test names, duplicates, and recalculated medians. Where they rely on the executor’s report, they say so.
It is essential not to broaden what the evidence establishes. A passing small observer program is different from capturing a real Cargo or linker process. An isolated test passing is different from a full suite passing under default parallelism. Participants repeatedly correct these distinctions in the room.
The number of environments is not the number of independent machines
Having multiple AI environments can look like having many machines. Containers and sandboxes, however, may share one physical host.
I asked participants for CPU core counts, memory, and storage status to decide where verification could run. The purpose was to understand available build space, concurrent work capacity, and tests requiring a native machine.
A heavy build in one environment on a shared host can affect another environment’s measurements. Before the current native macOS testing, participants coordinated so that the other environment on the same host would not start heavy work. Different environment names do not make a performance comparison independent.
We also asked participants to clean unused Cargo target directories, without removing verification binaries or reproduction logs still needed. They should announce cleanup targets, check that they are unused, and report recovered and remaining space. Disk management has become part of coordinating development.
Separate only the work that actually needs a native machine
A requirement for native macOS testing should not stop all related work.
We split work that requires an interactive native machine into a dedicated Issue, separating it from investigations possible on Linux or ordinary CI. Issue #1684 is an example. It distinguishes the native-machine dependency from unfinished work in the common path.
This is not a way to move a failure to another ticket and merge. Failures already observed in CI remain in the parent Issue. Marking a test as waiting for a machine does not fix the underlying bug.
A native-machine tester subsequently joined, making diagnosis possible. Participants are aligning the source, diagnostic patch, conditions, and number of runs. I do not want everyone launching their own expensive commands as soon as the machine becomes available.
A native host also cannot assume the same permission model as a sandbox. Owner approval of a verification plan and unrestricted permission for every operation on the host are different things. Deciding where work runs involves permitted operations as well as OS and CPU.
Push delivery does not automatically wake an idle AI
Initially, clients periodically fetched new messages. Even with no updates, requests continued. We changed this to push delivery.
PR #6 added SSE for CLI following and the HTML client. MCP gained receive, which waits for new messages. After disconnection, clients reconnect and recover messages after the last processed ID.
Delivery and the AI host beginning another inference step are separate concerns. A message reaching the server does not automatically start an inactive agent. The host must call receive, or a persistent subscriber must notify it.
Reconnection is not assumed to provide exactly-once processing. Consumers deduplicate by message ID and retain a cursor. If sending times out, the message may already have been stored, so history should be checked before retrying. The delivery tests also cover reconnection and fallback to older servers.
We updated one environment first, verified the new behavior, then authorized others to update. Keeping old clients working also made it easier to isolate problems if something broke.
The conversation UI changed as we used it
A chronological stream becomes difficult to follow as message counts grow. Replies appearing far from their original messages make their context harder to find.
We changed the UI to nest replies under posts. Relationships come from the payload’s reply_to field rather than guesses about the text. If the parent has not been fetched, it can be loaded later.
Rendering a large history all at once was slow, so threads and replies are displayed in limited batches, expanding as needed. New messages do not cause unrelated posts to be fully redrawn. A screen humans keep open has different needs from the agent communication format.
Other additions include unread counts in an inactive tab’s title, elapsed time since each participant’s latest post, and navigation from that time to the post.
These are not purely cosmetic. It helps to return quickly to the last communication when checking whether a participant is working or has not reported for a while. Still, an old message does not prove an agent has stopped. The UI supplies evidence; it should not infer and finalize task state by itself.
Bring choices and consequences to the human
I repeatedly ask two questions: “What is happening?” and “Is anything waiting for my decision?”
They are related but distinct. A chronological summary of everyone’s posts may not tell me what I need to decide. Conversely, a list of choices is hard to act on without knowing what is blocked.
Recently, I was asked whether an internal function exposure problem should be handled in a public Issue or a private Security Advisory. At the same time, I was asked whether macOS diagnosis should use paid CI capacity or a shared physical Mac.
I chose a public Issue for the former and the physical Mac for the latter. The decisions were posted back to the room, and the participants proceeded with filing and assignment. I do not need to specify every implementation step, but decisions about disclosure and cost should remain recorded.
Security is part of this workflow. cc-sakura currently targets trusted networks and does not require approval for a new participant who can reach the server. We have reminded participants to keep sessions and conversation databases out of public artifacts, check which conversations are sent to models, and create shared Gists as Secret. Secret Gists remain readable by anyone with the link; they are not equivalent to authenticated private storage.
More conversation does not automatically mean more progress
Using cc-sakura has made it clear that multi-agent development is about more than making agents understand one another’s words.
Tasks can have owners and still stall. Passing and failing tests can coexist on different environments. A machine can be ready while a small prerequisite patch is missing. New reports can keep arriving without increasing the number of mergeable changes.
A shared conversation makes these states visible. Other participants can point out unfinished scope, correct the conditions attached to a report, or suggest a handoff. I also find it easier to ask for a room-based picture of ownership and dependencies than to visit each separate conversation.
When I built Ruyka to understand code changes through diagrams, I was also interested in reconstructing a useful mental structure rather than producing more prose. This feels similar. What I want is not a large volume of messages, but an understanding of whose next action will move which work forward.
I have not measured an overall development speed multiplier for this approach. Excessive coordination is still a problem, and the way agents accept work is still evolving. More AI agents have not made human management unnecessary.
But they can reduce the need for me to carry every message. Agents exchange the information they need directly, while I focus on direction and decisions. Keeping communication flexible while retaining original messages, evidence, and ownership state is how we are trying to make that work. cc-sakura is the tool we are using to test this approach in Omoikane development.