While building cross-repository collaboration in Sunny, we had a practical decision to make. When a feature touches both frontend and backend, should one agent change both repositories, or should two agents split the work and message each other?
Both arrangements have an appeal. One agent can see both sides and update an interface alongside its caller. Two agents can work in parallel. If each side already has an active session, they can also keep using their own context.
A session holds an agent’s conversation history and execution. These were the two arrangements we compared:
| Arrangement | Code ownership | How the sides align |
|---|---|---|
| One session, two repositories | One agent reads and changes both repositories | Decisions and edits share one context |
| Two sessions, messaging | Each agent owns one repository | Messages carry API proposals and progress |
We ran three rounds. The first two compared these arrangements: first with an agreed interface, then with negotiation and existing histories. Round two exposed messages arriving too late. The third round compared delivery timing instead. Both arms in round three used two agents; there was no single-agent arm.
One task, two ways of working
What changes in each round?
Four runs per arm. Each square is one outcome, grouped by result, not run order. Do not pool the rounds.
One session or two?
One session, two repos
Two sessions, messaging
28 checks; all original artifacts re-evaluated after two display assertions were corrected. Two-session mean time was 12.6% lower; reported tokens were 2.17×.
Round one: with the interface settled, does splitting the work help?
The task was an OAuth integration across two repositories—the familiar “connect a third-party account” flow. State had to survive restarts. The implementation needed duplicate and concurrent callbacks, authorization failures, expiry, workspace isolation, legacy compatibility, and real-browser navigation, retries and cross-tab synchronization.
The repositories were small, but the behavioral requirements were not trivial.
Both arrangements used GPT-6.1 Sol at medium reasoning through Sunny’s real execution chain. Each pair began from the same starting point and received the same task. The two sessions shared one task budget; each agent did not receive a separate allowance.
After the agents stopped, an external executor ran frozen HTTP and browser checks. Scores were not returned to the agents for repairs. Execution and scoring were separate, but file-access boundaries were primarily instruction-based, not enforced by an operating-system sandbox.
Across four pairs and eight runs, every artifact ultimately passed all 28 checks. Two sessions were faster in three of the four pairs, with a mean implementation time about 12.6% lower. Their cumulative reported tokens were 2.17 times the single-session total. That includes cache reads, so it does not mean twice the bill.
This was a promising start for two sessions, with one important condition: both sides received a complete interface contract. Paths and fields were already specified. The agents mostly needed to implement their parts, not negotiate an API.
“All passed” also needed a correction. One implementation displayed Connected, while our evaluator demanded lowercase connected. We retained the original scores, corrected only two display-text assertions, and re-evaluated all eight unchanged artifacts. The agents did not retry or repair their work.[1]
The supported finding was narrow: with an agreed interface, both arrangements completed these tasks. Two sessions showed some parallel benefit and consumed more tokens. Next we needed to see what happened when the interface was still unsettled.
Round one: implementation time in each pair
Same starting point within each pair. All passed 28 checks.
Fresh 1
Fresh 2
Handoff history 1
Handoff history 2
Less time, but how much more usage?
Round one · Mean reported tokens across four runs per arm
| Mean per run | One session | Two sessions |
|---|---|---|
| Uncached input | 23,276 | 43,162 |
| Cache read | 266,464 | 592,128 |
| Cache write | 0 | 0 |
| Output | 10,231 | 15,029 |
Round two: can the agents negotiate and still finish?
In existing cross-repository work, the frontend may already depend on one set of fields while the backend must preserve established behavior. Old code and previous decisions are present before the new feature starts.
We began with a working OAuth product and added safe disconnection. The server had to check that a request still referred to the current revision, record the operation, and ensure that repeating the same request did not execute it again.
Consider a disconnect that succeeds, but whose response never reaches the browser. The user later creates a new connection. Retrying the old request must not disconnect the new one.
We used two scenarios, with two paired comparisons each:
- API negotiation: Local frontend and backend notes contained incompatible, unadopted API drafts. The agents had to settle paths and fields themselves. The evaluator read the final descriptor and checked behavior.
- Existing histories: Real Sunny sessions first completed maintenance work, leaving code, tests and full histories. The two-session arm resumed each native history. The single-session arm could read both complete history files.
Both arrangements could access the relevant information, but context organization and retrieval costs differed. That is part of the practical comparison; it prevents attributing every difference simply to the number of agents.
Again, there were four pairs and eight runs. Each task shared limits of ten minutes, 1.6 million cumulative reported tokens and 24,000 output tokens. We recorded two distinct outcomes: did the saved code pass acceptance, and did the task finish within budget? Passing code after a budget overrun still counted as a failed bounded delivery.
| Round two | One session, two repos | Two sessions, queued messages |
|---|---|---|
| Finished within budget and passed all checks | 4/4 | 0/4 |
| Saved artifact passed all 48 checks | 4/4 | 1/4 |
All four two-session runs reached a token limit. One had code that passed every check, but had not completed within budget.
Round two: where did each pair finish?
| Matched task | One session | Two sessions |
|---|---|---|
| API 1 | 48/48Within budget | 48/48Over budget |
| History 1 | 48/48Within budget | 21/48Over budget |
| History 2 | 48/48Within budget | 37/48Over budget |
| API 2 | 48/48Within budget | 21/48Over budget |
The failures were more informative than the ranking. In one run, each side implemented part of the task but ended with different paths and fields. In another, the descriptor files agreed, yet the frontend still called an old path and read an old field. Agreement in a message, agreement in a file, and agreement in working code needed separate checks.
We went back to the message timeline. The sending channel accepted 38 requests, but only 20 had entered the recipients’ persisted histories by the time the tasks stopped. One history scenario sent six messages; none entered before the stop.
The delivery mode was followUp: while the recipient was working, messages waited until its current execution settled. Both agents could spend a long time implementing and testing while an interface decision remained in the queue.
That gave us another possible explanation for the failures. A peer’s message might never have reached the context the agent was currently working with. Round two could not establish how much this explained, so we isolated timing in the next round.[2]
Round three: with two agents in both arms, does earlier delivery help?
This round no longer compared one agent with two. Both arms used a frontend agent and a backend agent, reusing round two’s starting code, histories, tasks and budgets. We changed only message delivery:
| Delivery mode | While the recipient is working |
|---|---|
Queued follow-up (followUp) | Append after the current execution settles |
Mid-execution delivery (steer) | Enter context at tool boundaries during execution |
Mid-execution delivery does not interrupt a running tool. A pilot also corrected an optimistic assumption: asynchronous forwarding means a message is not guaranteed to arrive before the immediately following step. One additional step may begin first.
We checked the chain with three consecutive tool steps. The queued message entered after the last tool; the steered message entered between tools two and three. Only after verifying that difference did we run the four formal pairs in the planned order.
At which step does the message enter?
followUp · Queued follow-up
- Tool 1
- Tool 2
- Tool 3
steer · Mid-execution delivery
- Tool 1
- Tool 2
- Tool 3
| Round three | Queued follow-up | Mid-execution delivery |
|---|---|---|
| Finished within budget and passed all checks | 0/4 | 1/4 |
| Saved artifact passed all 48 checks | 1/4 | 4/4 |
| Messages in recipient history before stop | 14/32 | 45/47 |
After acceptance, how many messages actually entered?
Round three · Entered recipient history before task stop
Among messages that entered history, median delay was about 154 seconds versus 10.7 seconds. Undelivered messages are absent from those medians, so the ratio is not a delivery-speed improvement. Entering history also means an opportunity to process a message, not proof of understanding.
One exchange captures the problem well. The frontend proposed A; the backend proposed B. Later, the frontend agreed to B, and the backend agreed to A. Both were accommodating. Their implementations still did not connect.
The mid-execution arm also experienced crossed confirmations. But conflicting information could enter while work was still underway. The agents subsequently froze a final version and implemented the same interface.
That artifact passed all 48 checks but exceeded the output budget. Another mid-execution run completed within budget. The queued arm also produced one 48/48 artifact, after exceeding its budget.[3]
The observations support improving delivery timing: final code aligned more often in the mid-execution arm. But only one of its four runs finished within budget. Negotiation and completion remained expensive.
Round three: all eight runs
| Scenario | Mode | Checks | Budget |
|---|---|---|---|
| API 1 | followUp | 21/48 | Output limit |
| API 1 | steer | 48/48 | Output limit |
| History 1 | steer | 48/48 | Total-token limit |
| History 1 | followUp | 21/48 | Total-token limit |
| History 2 | followUp | 28/48 | Total-token limit |
| History 2 | steer | 48/48 | Total-token limit |
| API 2 | steer | 48/48 | Completed |
| API 2 | followUp | 48/48 | Output limit |
Check the experiment before judging the agents
Some failures belonged to our experiment infrastructure.
A copied history was initially placed in the wrong directory and rejected by the production path check. We corrected its location and verified native restoration before using that same history. The last single-session run in round two was interrupted by lid sleep. System logs confirmed the interruption; we retained it and reran only that invalid attempt, not the normal budget failures.
Round one also left a test service running, and we changed a reporting script before the run had fully exited, triggering source-change protection. After that, we froze the entire checkout—including reporting scripts—until execution and cleanup finished.
These records must remain separate from model failures. Otherwise an environment interruption can become a model failure, while the human work needed to repair the experiment disappears from the score.
How much does passing every check tell us?
Round one had already exposed an evaluator mistake. Even when an artifact passed every check, we still needed to ask which behaviors the checks missed.
We conducted a post-hoc acceptance audit without rerunning the agents. We reviewed requirements and existing coverage, then fixed 12 additional boundary combinations: whether an invalid request disturbed pending authorization, whether workspace receipts remained isolated across restarts, and whether retrying an old request could disconnect a newly established connection after a lost response.
We calibrated the checks against a reference implementation and four deliberately broken variants. The reference passed all 12; the corresponding checks caught each broken variant. We then evaluated copies of all 16 saved artifacts from rounds two and three. Hashes of the originals and execution copies were unchanged before and after.
All ten artifacts that originally passed 48/48 also passed the 12 new checks. The other six stayed in the audit: four had incompatible descriptors, passed ten backend checks and could not run two browser checks; one lacked a descriptor, blocking all 12; another had a frontend integration mismatch and failed two browser flows, consistent with the original report.
16 saved artifacts, 12 additional boundary checks
Each square is one saved artifact, grouped by outcome, not run order.
The audit found no new counterexample among the previously passing artifacts. It strengthens confidence in these particular boundaries, not in complete coverage. The reviewer knew the earlier outcomes. This was a post-hoc audit, not a blind test or independent-team certification.[4]
We did not manually repair candidate code and then count it as model success. But people participated in experiment design, evaluator calibration, infrastructure recovery and record review. We did not consistently measure the full human review and rework time, so we cannot report production end-to-end delivery time.
Back to the original choice
For a new task spanning two repositories, we currently lean toward explicitly attaching both to one session. When interfaces still need to change, a shared context can avoid some cross-session confirmation. This is a product judgment informed by these observations, not proof of a universally best arrangement.
When frontend and backend already have active sessions, preserving their histories and continuing the split remains worth exploring. But messaging needs more than a “sent” state. We need to distinguish queued, entered into the recipient’s context, and explicitly answered. Round three also shows that earlier delivery can still leave agents negotiating repeatedly and exhausting the budget.
The scope is small. Each round has only four pairs. Tasks, budgets and prompt versions changed between rounds one and two, so they cannot be pooled into one leaderboard. Existing histories came from preceding maintenance tasks, not months of organic use. One two-session run in each of rounds two and three borrowed host Playwright for self-testing, deviating from the built-in-dependencies-only premise; both remain in the results and disclosures. Mandatory handshakes and designated coordinators were not tested.
We started by comparing one agent doing the work with two agents splitting it. Running the experiments showed that “the agents can message each other” is not yet a complete experimental condition.
Where the message arrived, when it arrived, and whether both sides actually changed their code accordingly all need to be checked in the record.
Methods and data
[1] Round one: agreed API, fresh sessions and controlled handoff histories. Two display assertions corrected; all eight original artifacts re-evaluated.
[2] Round two: API negotiation and real maintenance histories; four pairs, eight runs. Budget failures retained; infrastructure-invalid attempts recorded separately.
[3] Round three: two sessions in both arms, reused starting snapshots, followUp versus steer only.
[4] Supplemental audit: 16 saved artifacts, 12 added checks; 170 passed, two failed and 20 blocked by prerequisites.
Scope and reviewable results
24 formal model runs, eight per round. The supplemental audit re-evaluated 16 saved artifacts without new inference. GPT-6.1 Sol · medium · pi 1.4.0. The message bridge exercised production prompts, parser and lifecycle, but remained an experimental adapter, not a shipped messaging product.
Round one shared limits: 480 seconds, 1.2 million cumulative reported tokens, 20,000 output tokens and 24 messages. Rounds two and three: 600 seconds, 1.6 million reported tokens, 24,000 output tokens and 32 messages. Reported usage includes cache reads and is not dollar cost. Round three reused round two’s starting snapshots, not its failed implementations.
Round three alternated order across four pairs: followUp → steer, steer → followUp, followUp → steer, steer → followUp. Failed runs and undelivered messages remain in the record.
The download contains selected per-run results and method summaries. Raw sessions, credentials and local paths are not public. Full raw records are retained locally; a public summary does not replace raw-trace review.
Download per-run results and supplemental audit summary (JSON) ↓
AI-assisted writing. Four pairs per round; an engineering record, not a general capability ranking.