Sunny Code

One agent across two repositories, or two agents working separately?

Three rounds of cross-repository experiments: implementation, API negotiation, and when messages reach the other agent.

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:

ArrangementCode ownershipHow the sides align
One session, two repositoriesOne agent reads and changes both repositoriesDecisions and edits share one context
Two sessions, messagingEach agent owns one repositoryMessages 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

One sessionTwo sessionsAgentAgentAgentFrontendBackendFrontendBackendShared contextMessage coordination↔ Messages
Schematic, not a measure of time or tokens. Both arrangements share a task-level budget.

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

Artifact passed acceptance4/4
Completed within budget + passed4/4

Two sessions, messaging

Artifact passed acceptance4/4
Completed within budget + passed4/4

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

One session
230.7 s
Two sessions
282.9 s

Fresh 2

One session
245.9 s
Two sessions
201.7 s

Handoff history 1

One session
327.9 s
Two sessions
236.8 s

Handoff history 2

One session
280.9 s
Two sessions
226.9 s
Every bar uses the same 0–360 second scale; this is not end-to-end acceptance time. Two sessions were faster in 3 of 4 pairs, with a mean about 12.6% lower and 2.17× reported tokens.

Less time, but how much more usage?

Round one · Mean reported tokens across four runs per arm

Uncached inputCache readCache writeOutput
One session299,971
Two sessions650,318
Mean per runOne sessionTwo sessions
Uncached input23,27643,162
Cache read266,464592,128
Cache write00
Output10,23115,029
Both bars share one scale. Usage accumulates across model calls, including repeated cache reads; it is neither context-window size nor dollar cost. Means are rounded; cache writes were zero.

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 twoOne session, two reposTwo sessions, queued messages
Finished within budget and passed all checks4/40/4
Saved artifact passed all 48 checks4/41/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 taskOne sessionTwo sessions
API 148/48Within budget48/48Over budget
History 148/48Within budget21/48Over budget
History 248/48Within budget37/48Over budget
API 248/48Within budget21/48Over budget
Numbers are passed checks, not completion rates. Every two-session run exceeded budget, including the first 48/48 artifact. History 1 ended with incompatible descriptors; History 2 agreed in the descriptors but not in the frontend implementation. API = negotiation.

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 modeWhile 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

  1. Tool 1
  2. Tool 2
  3. Tool 3
  4. Message

steer · Mid-execution delivery

  1. Tool 1
  2. Tool 2
  3. Message
  4. Tool 3
Order observed in the three-tool pilot, not drawn to elapsed-time scale. Neither mode interrupts a running tool. Asynchronous forwarding means steer does not guarantee delivery before the immediately following step.
Round threeQueued follow-upMid-execution delivery
Finished within budget and passed all checks0/41/4
Saved artifact passed all 48 checks1/44/4
Messages in recipient history before stop14/3245/47

After acceptance, how many messages actually entered?

Round three · Entered recipient history before task stop

Queued follow-up14 / 32
Not entered before stop: 18
Mid-execution45 / 47
Not entered before stop: 2
Blue means entered; gray means not entered before stop. Bars show within-arm proportions, with different message totals. Entry is not understanding. Median delays of 154 s and 10.7 s include only entered messages, not overall delivery speed.

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

ScenarioModeChecksBudget
API 1followUp21/48Output limit
API 1steer48/48Output limit
History 1steer48/48Total-token limit
History 1followUp21/48Total-token limit
History 2followUp28/48Total-token limit
History 2steer48/48Total-token limit
API 2steer48/48Completed
API 2followUp48/48Output limit
API = negotiation; History = existing maintenance history. Formal run order retained. An artifact passing 48/48 can still exceed its budget; it is not counted as a completed bounded delivery.

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.

All passed12 passed · all previously 48/48
10
Contract mismatch10 passed · 2 blocked
4
Missing descriptor12 blocked
1
Frontend mismatch10 passed · 2 failed
1
192 planned check instances: 170 passed, two failed, 20 blocked. Blocked checks lack prerequisites; they are not 20 independent defects. The two failures belong to one frontend-mismatch artifact. No candidate code was repaired for this audit.

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.