Tech11 min read

Codex on Astra ate my weekly quota in half a day, so I rebuilt the orchestration

IkesanContents

I have a work project that runs on Codex, and until now it ran on Sol.
Then Astra arrived. I swapped it in without much thought, handed the rest to the Goal skill, and went to bed.
In the morning it had stopped.

It had started right after the weekly limit reset, and it burned through 100% in under half a day.
That was bad enough that I went and looked at what was going on.

Two pointless resets

To pick up where it stopped I reset once, this time with the reasoning effort turned down.
The result was about the same, and I ended up resetting again.

What was it spending all that on?

This project doesn’t call the main model directly. AGENTS.md sets up an orchestration where the top model directs lower-tier models to do the work.

Normally nothing should be running entirely on Astra. Sol behaved fine under that setup, but Astra had promoted every task to itself.
It had read AGENTS, but it kept deciding that doing things its own way would work better and changed the plan on its own.

That alone would be tolerable, but watching it, the completion checks and polling against the lower-tier models were intense.
If a Python script were doing that on a timer it would be fine. Doing it as LLM calls is what was forcing context compaction.

In short, for anything that isn’t a one-shot request, like continuing existing work or restructuring, using Astra the way I used Sol burns far too much.

The question became whether locking it down much harder would fix it. I spent a day hammering on that.

Tuning the polling interval

My Codex CLI is 0.153.4. There is a minimum wait setting, so I raised it.

[features.multi_agent_v2]
enabled = true
min_wait_timeout_ms = 120000
default_wait_timeout_ms = 120000

Since this is a minimum, pushing it too far means it may stop checking at all. This needs tuning per environment by actually running it.

I changed AGENTS.md to match.
Checking on a subagent’s work defaults to a 120-second wait, and for work likely to exceed 120 seconds it sets at least double that, within the available ceiling.
Nothing below that wait is allowed.
The baseline is to wait for completion notices from the worker and manager agents, and only go fetch state when a human explicitly asks for a status right now.

Spinning up an agent just to monitor a process is flatly forbidden.
Process monitoring is the runtime’s job. Not spawning a pointless agent means not paying for one.

In my environment the default was 60 seconds. For this work, 60 seconds is far too short; waiting tens of minutes would be fine. But I don’t know in advance where a task will end, so raising the floor and letting the ceiling stretch with the size of the job seemed like the more flexible rule, and that’s how I set it.

Separating the roles

For now the layout is Astra as orchestrator, a Sol phase manager under it, Luna, Terra, and Sol workers under that, plus an independent Sol reviewer.
In human organizations too, nothing good comes from the top meddling with the bottom.
The instruction is put strongly, “the orchestrator behaves as a senior manager, not an implementer."
"It’s small” or “it’s faster if I do it myself” are not accepted as reasons.
Conversely, if the task is clear and bounded, the manager in the middle is skipped.

RoleOwnsDoes not own
OrchestratorGoal, priorities, authority, allocation, final judgment on evidenceImplementation, running tests, deploys, record updates
Phase managerAllocation, dependencies, fixing prompts and paths, correction loopsImplementation, running tests, deploys, record updates
WorkerOne task and the short check that goes with itSpawning other agents, decisions outside scope
ReviewerReading the actual diff and evidence, pass/failExpanding the spec, unrelated cleanup

Hand off summaries, not the whole process

Looking only at the orchestration, this seemed like a clean structure. But thinking it through, if the middle-management manager keeps ingesting every implementation result, test output, review, deploy, and post-deploy check, Sol becomes a second Astra.
That’s overwork, and the context is very likely to overflow.

The summarization that used to happen only right before handing up to Astra now happens at every handoff between agents.

Cut what isn’t needed

If only a summary is passed, the only judgment material is what the previous model said, and no instruction can be built on it.
For example, a change can be fixed in a commit and handed over, or a handoff Markdown works too. Something has to be passed along.

The point is not to send up material that the decision doesn’t need. The originals are saved as referenceable artifacts, and what managers and the orchestrator receive is the summary plus a pointer.
This also keeps them from re-scanning files. The whole idea is to not overflow the context, not concentrate work in one agent, and break it into short jobs.

The reviewer is the one exception. It does not judge from the summary; it opens the actual diff and evidence from the pointer itself.
If review runs on summaries too, “looks OK I guess” is all that travels upward, nobody has looked at the contents, and whether it’s really OK is unknown.

A commit is not required at every step.
Sometimes a diff hash is taken from a dirty working tree, so which commit is the base, which diff is uncommitted, which environment, and as of when are all written separately.
Branches or worktrees are an option, but they don’t fit well with parallel work under orchestration, so this is the operational compromise.

A phase manager is terminated once its bounded phase is done. If it carries unrelated task history into the next phase, the manager goes wrong somewhere.

Constrain the messages between agents too

Having them write summaries is fine, but free-form text will very likely get long anyway.
Each agent writing in whatever style it likes is a problem.

The format of the summary JSON returned upward is now enforced.

FieldContentLimit
statusOne of completed / needs_action / blocked
summaryWhat happenedOne sentence, 120 chars max
changesWhat changedMax 3 items, 80 chars each
evidenceReferences and the verification outcome at eachMax 3 items, references never truncated
risksWhat’s dangerousMax 3 items, 80 chars each
unverifiedWhat hasn’t been confirmedMax 3 items, 80 chars each
next_actionWhat comes nextOne sentence, 120 chars max, or none
escalationDecision to push up and the recommendationnull, or 120 chars max

Empty array if nothing applies. If more than 3 items, save everything to an artifact and leave only the omitted count and a short reference like which evidence index. Critical failures and unverified items are never left out. Dropping those breaks things.

{"status":"completed","summary":"Changed default wait to 120s and replaced the wait section of the instructions","changes":["Added wait keys to global config","Replaced wait/notification section in instructions"],"evidence":["<commit hash> / index test pass"],"risks":[],"unverified":["Running session still has the upstream 60s constraint"],"next_action":"Confirm the effective wait in a new session","escalation":null}

completed in status means only that the assigned scope is done.
It doesn’t wait for overall completion, but it also doesn’t flip just because a review passed, since that causes trouble later.
Review, deploy, and post-deploy verification each already have a detailed JSON of what was checked. Those stay as the originals, and the common 8 fields point to them through evidence.

Astra ends up demoted from orchestrator

Finally I reconsidered whether Astra needs to sit permanently as orchestrator.
The job at that level is narrowed to managing goals and judging evidence. It does none of the heavy work, no exploration, no implementation.
Is there really a reason to keep the heaviest model there? For this work at least, no.

The default is now Sol medium as orchestrator.
Sol medium as phase manager when one is needed, Luna, Terra, or Sol for implementation depending on the weight of the task,
and a fresh Sol medium instance with no inherited conversation as reviewer.
Astra is out of the main line of work and kept only as the exception advisor.

flowchart TD
  R[Orchestrator Sol medium] --> M[Phase manager Sol medium]
  M --> W1[Worker Luna]
  M --> W2[Worker Terra]
  M --> W3[Worker Sol]
  M --> RV[Reviewer Sol<br/>no inherited conversation]
  W1 --> A[Artifacts<br/>diffs, logs, originals]
  W2 --> A
  W3 --> A
  A --> RV
  W1 -. receipt JSON .-> M
  W2 -. receipt JSON .-> M
  W3 -. receipt JSON .-> M
  RV -. receipt JSON .-> M
  M -. receipt JSON .-> R
  R -. exceptions only .-> AS[Astra advisor]
  AS -. advice .-> R

The conditions for calling Astra are spelled out.

TriggerCondition
DisagreementThe manager and reviewer materially disagree. The same material finding survives two correction and review cycles
Scope changeThe scope of the work itself has to change
High-impact changePublic API contract, DB schema, auth, or migration changes. Irreversible or destructive impact is concretely known
Unresolved designAfter bounded consideration, multiple viable designs remain and nothing settles
Insufficient evidenceThe evidence needed for the requested completion is missing. Still explicitly blocked after checking locally resolvable facts

The non-triggers are written out just as explicitly.

Not a triggerHandling
Seems hard, seems slowDifficulty and time alone are not conditions
Two minor findings piled upCounted separately from a material finding surviving two cycles
Evidence is missingEither more verification or incomplete. Astra cannot waive a mandatory check
Decisions belonging to the userAstra does not act on the user’s behalf and does not implement
Re-escalation on unchanged factsOnce advice is returned, responsibility goes back to Sol

Make this fuzzier, “call me when things look like this,” and you get one of two extremes, never called or called constantly.
This kind of thing is generally better judged mechanically.

The accident when actually running it

Running this setup for real, the number of issues fetched from the semi-automated Backlog didn’t match the number I had specified.
Sol started working anyway. The reason it reported was that the fetch script lacked the related-category filter.

But the count clearly didn’t match what I said, so I wanted a check right there. Sol going ahead without one felt a bit weak on reasoning.
When Astra was orchestrator, it re-ran the fetch even with the same flawed script.

Reasoning effort probably plays into it, so I raised the orchestrator to Sol high.
Before the Astra swap it was running on high anyway, so this may just be the sensible setting.

Behavior after switching to high

After the change to high, when it re-fetched Backlog it recognized the issues I had added midway, looked at dependencies against existing issues, and reordered the work.
It first said it might push the added issues to the end, then judged partway through that they could be handled, inserted them, and explained why they couldn’t run in parallel.
At least in this case, the weak judgment seen on medium was absent and it behaved the way I expected, I think.

OrchestratorBehavior observed
Sol / mediumMissed the mismatch between the worker’s report and what the human stated
Sol / highRe-fetched Backlog and reordered the work including added issues
AstraCould always re-fetch and re-judge, but consumption is very high

Under the rebuilt Sol setup, about 90 minutes of running with additional instructions issued midway showed under a 1% drop in the displayed usage.
This isn’t a strict comparison against the Astra run, so I can’t declare it dramatically more efficient, but it’s at least better than letting Astra run free.
Switching from medium to high hasn’t shown any visible difference in the usage display so far either.

High is used in exactly one place, the orchestrator slot where Astra used to be.
The phase manager stays on Sol medium, implementation on Luna, Terra, and Sol, the reviewer on Sol medium, and there’s no reason yet to push high further down.
I can’t say much more until I see what comes back from running it, but if only the really bad stuff gets escalated, the reviewer will have something to say.