The agent09
The operator
The company is run by an agent that can only choose among actions code has already allowed. Every tick it reads a snapshot of the books, code enumerates the candidate actions and why the rest are not allowed, the model picks and explains, code re-checks right before executing, and one signed ledger entry records all of it.
Snapshot → candidates → guards → one entry
- Snapshot.
companySnapshot()reads revenue (thepaymentstable), reserves (revenue wallet USDC and Circle Earn vault positions on Arc, Circle wallets), obligations (treasury invoices and daily headroom), screening volume and p95 latency, current prices and modes. Every section degrades tonullwith an error string; the function never throws. - Candidates.
candidateActions()is deterministic. It lists what is allowed now with a reason, and lists every rejected action with its reason.HOLDis always a candidate. - Choice. The model (same client as the analyst) must call
submit_planwith candidate ids, a rationale and the next check interval. Unknown ids are dropped,HOLDis never mixed with actions, an empty choice falls back to the deterministic plan, the interval is clamped to 15 min–6 h, the rationale is cut at 160 words. Without an LLM provider (LLM_PROVIDER=0g+ZEROG_API_KEY, orANTHROPIC_API_KEY) the deterministic plan runs withmeta.fallback = true. - Execution. In the order payouts → prices → liquidity,
validateAction()re-derives the candidates and rejects anything no longer allowed, then each executor honours its own mode (dry-run or live). - Record. One
OPERATORentry with the snapshot summary, candidates, rejected, chosen with results, rationale andnextCheckAt. The company page renders the last ten.
// kind: "OPERATOR" - one per tick, hash-chained like every other decision
{
"type": "OPERATOR",
"snapshot": { "revenue": { … }, "reserves": { … }, "obligations": { … }, "screening": { … }, "pricing": { … }, "mode": { … }, "errors": { … } },
"candidates": [{ "id": "c1", "type": "RUN_PAYOUTS", "params": { "pendingInvoices": 2, "amountUsdc": 35 }, "reason": "…" }, { "id": "c2", "type": "HOLD", … }],
"rejected": [{ "type": "SWEEP_TO_YIELD", "reason": "balance 2.02 below float 5 but no vault to redeem (effective float 5 = 5 cushion + 0 due 48 h)" }, { "type": "ADJUST_PRICE", "reason": "x402 disabled …" }],
"chosen": [{ "id": "c1", "type": "RUN_PAYOUTS", "ok": true, "result": { "mode": "dry-run", "paid": 1, "limited": 1, "held": 0, … } }],
"rationale": "…plain English, ≤ 160 words, cites the numbers in the snapshot…",
"nextCheckInSeconds": 1800, "nextCheckAt": 1760001800,
"meta": { "model": "0g:claude-opus-4-8", "fallback": false, "latencyMs": 2140, "guardNotes": [], "paused": false, "chainId": 5042 }
}What it may decide
| Action | Allowed when | Executor |
|---|---|---|
SWEEP_TO_YIELD | float and obligations are both known, usdc − (YIELD_FLOAT_USDC + obligations due in 48 h) ≥ min sweep (1 USDC), and a Circle Earn vault passes the rules (params carry the vault) | Circle Earn tick from the Circle revenue wallet (dry-run unless YIELD_MODE=live) |
REDEEM_FROM_YIELD | usdc is below the effective float, a vault position is held, and that vault has the liquidity to pay it | same |
RUN_PAYOUTS | pending invoices exist, daily cap has headroom, and a live treasury is not at zero reserves | Guarded Payouts tick (dry-run until TREASURY_MODE=live) |
ADJUST_PRICE | x402 is enabled; raise when paid calls in 24 h > 50 and p95 ≤ 8 s, lower when outside paid calls in 48 h = 0; inside bounds, step and cooldown | operator_pricing table, read per request by the paid routes |
HOLD | always | no-op |
A sweep is never planned blind: unknown reserves, unknown obligations or missing Earn vault metadata reject it. A paused service (COMPLIR_PAUSED=1) rejects everything but HOLD. Ledger entries written before 2026-10-14 call these actions SWEEP_TO_USYC / REDEEM_FROM_USYC.
Where idle revenue goes
The yield backend is Circle Earn: Morpho USDC vaults on Arc, discovered through Circle's Earn API (the service behind @circle-fin/earn-kit) and used as plain ERC-4626 from the Circle revenue wallet (USDC.approve → vault.deposit(assets, wallet); vault.redeem(shares, wallet, wallet)).
| Rule | Why |
|---|---|
| refuse vaults Circle flags low_liquidity | the revenue has to come back on demand; Circle's illiquid flag is the first gate |
| available liquidity ≥ 2× (deposit + position) | we could redeem the whole position twice over right now |
| no RED warning, not unlisted, Circle Sentinel, TVL ≥ 100k, APY > 0 | skip timelock-less test vaults and unreviewed curators |
| best current APY among the rest | today: Steakhouse Prime USDC 0xbeef0016…7298 |
| float = YIELD_FLOAT_USDC (5) + obligations due 48 h; ≤ 25 USDC per deposit | payouts and refunds never wait on a redeem; stays under the wallet's per-tx policy |
Each executed move is its own ledger entry (OPERATOR · YIELD_SWEEP / YIELD_REDEEM with tx hashes); a plan that moves nothing is NOOP · YIELD_PLAN. The /company Reserves card shows the position per vault (name, address, shares, assets, APY, liquidity). Details: docs/YIELD.md.
Pricing bounds
- screen
- 0.005 – 0.05 USDC (config default 0.01)
- watch
- 0.02 – 0.2 USDC / 30 days (config default 0.05)
- Step
- at most ±20 % per change; the operator proposes 10 % steps
- Cooldown
- at least 6 h between two changes of the same price
- Raise trigger
- OPERATOR_RAISE_CALLS_24H (50) paid calls in 24 h and p95 latency ≤ OPERATOR_MAX_P95_MS (8,000 ms)
- Lower trigger
- zero outside paid calls in 48 h and price above the floor
validatePriceChange() is a pure guard (bounds → step → cooldown) and runs again inside setPrice(). The current price is what the 402 advertises; a client that signed for less is rejected before anything runs.
Schedule and visibility
- Cron
- hourly at :30 (
/api/internal/operator, bearerCRON_SECRET) orworker/operator.ts - Next check
- the agent's own interval (900–21,600 s) is recorded; the worker honours it, the Vercel cron is the floor
- Read
GET /api/v1/company- snapshot + last ten decisions, cached 30 s · /company · /ledger kind OPERATOR- Model
- LLM_MODEL (today
claude-opus-4-8on the 0G Compute Router;ANALYST_MODELfor Anthropic), temperature 0.2, tool-forced structured output