The agent runtime
How the agent actually runs: one loop per account, two kinds of thread, three inboxes, and a set of rules about finishing what it started.
Two kinds of thread
Section titled “Two kinds of thread”One loop per active account, in a worker thread. Threads are the unit of work, and there are two kinds.
Chat threads are persistent and started by you — up to eight per session. Alert threads are ephemeral: something in the market woke them, they do one job, and they complete with a summary. Also capped at eight concurrent; past that, alerts queue and the queue depth is shown rather than silently absorbed.
The distinction that matters is context. A chat thread has the conversation. An alert thread has only the briefing it was spawned with — which is why that briefing is a structured document rather than a sentence, and why truncating one without a read-back is treated as a bug rather than as a saving.
Waking up
Section titled “Waking up”Three inboxes, all drained by the same consumer:
harness:chat:inbox:{accountId} a message you sentharness:alerts:inbox:agent_{accountId} price, time and trade triggersharness:thread-queue:{threadId} what arrived mid-turnQueued messages are coalesced, not replayed. A turn that finishes drains its queue into one combined message rather than running three turns back to back. Three turns would each answer part of a thought and none of them the whole one.
What an alert thread must do before it may finish
Section titled “What an alert thread must do before it may finish”An alert thread that wakes, thinks and does nothing is worse than one that never woke: it spent a model call and left the position exactly as exposed. So completion is guarded, with retries.
- A stop-verification thread must actually call the verification tool.
- A trade alert must edit the setup or reduce the position for the trade that woke it.
- After a trade alert, a target watcher must exist — otherwise a position that just round-tripped has nothing watching the level it came back from.
This is the runtime enforcing that an agent finishes its job, rather than trusting it to. The three checks are the three ways it was observed not to.
Subagents and structured output
Section titled “Subagents and structured output”Some work is delegated to a subagent with a narrow job and a fixed output shape — reading a chart, a web page, a prediction market. The caller needs a structure back, not prose, because the result feeds arithmetic.
Models do not reliably emit valid JSON, so the boundary is defensive rather than trusting:
- JSON is extracted by matching balanced braces, not by a greedy regular expression — a regex takes the first brace and the last one in the document and returns the prose between them.
- The result is validated against a schema: enums, types, required fields. Failures come back as a list rather than a boolean.
- An invalid result is retried with the specific errors appended, so the second attempt is told what was wrong rather than asked again.
- A final attempt that still does not validate is passed through carrying its own warnings, so a caller can see that the shape is imperfect instead of receiving something that looks clean.
That last point is the pattern worth naming: a partial result labelled as partial is usable, and a partial result presented as complete is a lie the caller cannot detect.
The background loops
Section titled “The background loops”| Loop | Cadence | What it does |
|---|---|---|
| Thread consumer | continuous | Drains the inboxes, sweeps the time-alert and trade-timer hashes, spawns turns |
| OAuth refresh | every 5 min | Renews tokens inside their expiry buffer, single-flighted because they rotate |
| Brownout recovery | every 60s while active | Polls the primary model provider and clears the brownout when it answers |
| Event stream | event-driven | One subscription per live browser |
| Step-down timer | 1–60s during a brownout | Walks a position down while no model can be reached |
Brownouts
Section titled “Brownouts”A model provider can fail while you hold a position. That is not a reason to do nothing, and it is not a reason to improvise either.
When the primary provider stops answering, the runtime enters a brownout: it degrades to a fallback where one is configured, and recovery polls the primary once a minute rather than retrying every request. Where a position must be reduced and no model can be reached at all, a timer steps it down without one — the decision to exit was already taken by the levels on the trade, and executing it does not require a language model.
Permission gating
Section titled “Permission gating”Every tool that changes something is gated. The gate is per account and per action, and the settings are the ones you see in the account header — whether the agent may place a trade, whether it may only reduce, whether a proposal is applied automatically or waits for you.
A refused action returns a result explaining the refusal rather than an error, for the same reason the drawdown lockout does: an error invites a retry, and a permission boundary is not a transient failure. What the harness can do to a live position, and under which permission, is Execution.