Skip to content

Generate a suite with one writer per use case, and persona values the platform understands - #63

Draft
KarthikAvinashFI wants to merge 2 commits into
feat/hosted-harness-e2e-runtimefrom
feat/parallel-scenario-generation
Draft

Generate a suite with one writer per use case, and persona values the platform understands#63
KarthikAvinashFI wants to merge 2 commits into
feat/hosted-harness-e2e-runtimefrom
feat/parallel-scenario-generation

Conversation

@KarthikAvinashFI

@KarthikAvinashFI KarthikAvinashFI commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

What this changes

Scenario generation could not produce a suite of the size people ask for, and the reason was not speed.

not_ready() refused to save when two scenarios shared a use_case, because coverage is counted by use case and a duplicate overstates it. An agent contract names a limited number of use cases, so a request for forty produced a suite that could never be saved. Separately, generation ran in one session at roughly three turns per scenario against a single budget, so a large request ran out of turns partway and lost everything it had written.

Both are addressed here.

Coverage is counted by use case and branch. A scenario carries a new branch field: the condition that makes it different from its siblings in the same use case. Several scenarios sharing a use case is now normal, which is what branches are for. Two scenarios agreeing on both is still refused.

A suite is written by one session per use case, several at a time. write_in_parallel() splits the requested count across the contract's use cases, gives each writer its own session and its own turn budget, and bounds how many run at once. Wall clock becomes the slowest use case rather than the sum of all of them. A new generate_suite tool exposes this to the open session, so asking for a suite in chat fans out instead of writing one scenario at a time.

Writers run submit-only. save_scenarios regenerates the index and deletes any folder it does not know about, so two writers saving at the same time would delete each other's work. They keep their scenarios in memory and the caller merges and writes once, dropping the collisions the writers could not see.

Persona values come from the platform. A generated persona used to be free text, so nothing matched what the platform understands, no behaviour guidance attached, and the accent selected no voice. The fields are now constrained to the platform's own vocabularies and checked on submit, and the behaviour guidance is read from the platform's tables rather than restated here, so a caller behaves the same way in a harness run as in a platform simulation. Where those files are not mounted, personas still render without guidance rather than failing.

Progress is visible while it runs. The fan-out writes its state as it goes and GET /api/generation serves it, so a page can show which use cases are being written and how many of their share have been proved. A scenario counts only once it has cleared all three gates.

How a suite is decided, not just divided

The first version split the count evenly across use cases. That is a poor plan and it showed: a use case with one real branch padded to fill its share while one with six under-covered, and nothing ever read the finished suite to notice.

The plan is now consumed rather than discarded. The session talking to the person already writes one, because the skill asks for it. It now passes that plan in: each slice names its use case, the angle it should take, how many scenarios it is worth, and why. Even division remains the fallback for a caller that supplies nothing.

Each writer is briefed rather than templated. It is told its own slice, what its siblings hold so it stays out of their way, the caller spread it should start from, and what a scenario has to be before submitting: every value read from the world rather than invented, an instruction that is a circumstance rather than a script, a setup that makes true whatever the instruction presumes, a solution rehearsed with try_calls first, checks that assert the right call with the right arguments, and a scenario a competent agent could plausibly fail.

The finished suite is reviewed. Writers are blind to each other and the merge only removes collisions, so a use case that came back short, or a branch every writer assumed somebody else had, survived to the end unnoticed. One session now reads the whole suite and names what is missing, and only those gaps are re-delegated. Bounded to one round, because a reviewer asked twice will always find something smaller to say.

A large ask is served a batch at a time. Fifty scenarios in one pass is the ceiling and eight concurrent writers is the other, both overridable. Asking for a thousand is reasonable to want and unreasonable to do at once: the batch comes back, the rest is offered, and the session is told to show what it has and ask whether to carry on rather than silently looping.

Measured on a deliberately uneven plan (3 scenarios on the rule under pressure, 1 on the ordinary path, 2 on the branch that cannot be completed): 6 scenarios in 294 seconds, and the angles reached the output. The pressure slice produced three scenarios about a caller pushing to skip a step; the ordinary slice produced the clean path; the refusal slice produced the two payment failures.

The review pass was exercised separately against that finished suite: 8 gaps in 43 seconds, each naming real records and real tools, including one no individual writer could have seen, that both existing payment-link scenarios were failure variants and the success path was missing.

Measured

Against the Uber ride-voice contract, which has 14 use cases:

model slices at once kept time
Opus 4.7 3 3 6 of 6 260s
Haiku 4.5 6 6 12 of 12 382s
Haiku 4.5 14 7 40 of 40 under 10 minutes

The 40-scenario suite came out as 14 distinct use cases and 40 distinct use case and branch pairs, with no duplicates. Every scenario cleared all three gates. Concurrency was confirmed by counting processes rather than inferred.

Persona spread was measured too, because the first attempt at it failed. Telling each writer to vary its callers changed nothing, since a writer producing one scenario has nothing to vary and writers cannot see each other. Dealing the spread out at plan time works:

before after
distinct personalities 5 of 12 7 of 12
most common personality 58% 17%
distinct accents 3 5

Testing

  • 262 harness tests pass, including the surface conformance test that checks the published tool set against TOOL_NAMES.
  • tests/simulation_contract/test_contract_native.py::test_legacy_manifest_without_block_byte_identical fails, and fails identically on the base branch without these changes.

Notes for review

  • The persona guidance and vocabulary are read from files supplied by path, so this repository does not name where they live. Without them, persona validation stays off rather than rejecting everything.
  • generate_suite is offered only to a session that can save. A writer that is itself one slice cannot fan out again.
  • Based on feat/hosted-harness-e2e-runtime rather than dev, since it builds on the harness layout introduced there.

@KarthikAvinashFI KarthikAvinashFI self-assigned this Aug 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant