--- name: desk-autonomous-loop description: The unattended cycle the autonomous desk runs on a schedule - pre-flight, book and protection, reconciliation of open sends, market scan, idea generation from live strategies and discretionary judgement, sizing, policy gate, single send, reconciliation and journal - and how to run it as a Grok Bot routine or a headless Claude Code session. Use to start, schedule, run, debug or explain the loop. license: MIT metadata: version: "1.0.0" author: SWC Studio category: desk --- # Autonomous loop The desk trades without anyone watching by running the same lifecycle as before on a timer, with the user's approval replaced by the policy gate (`desk-autonomy-policy`). One cycle is bounded, ordered and written down. Anything the cycle cannot finish with fresh evidence stops at that stage and says so in the journal. ``` pre-flight -> book & protection -> reconcile open sends -> scan -> ideas -> size -> gate -> send -> reconcile -> journal DL RM ET MA/RA ST RM gate ET ET TR ``` ## 1. One cycle **Pre-flight (Desk Lead).** Validate the policy (`policy_gate.py policy`), read the halt state (`kill_switch.py status`), `desk.md`, `risk-limits.md`, the previous cycle's journal entry and `autonomy/heartbeat.json`. Mode `off` ends the cycle after a one-line journal entry. A halted desk continues in reduce-only mode: steps 2 and 3 run, step 5 is skipped, and only `close` and `protect` tickets are written. **Engine check (Desk Lead).** Read `autonomy/engine-heartbeat.json` and the tail of `autonomy/engine.log`. If the strategy engine's heartbeat is newer than two engine intervals and its status is `trading` or `observing`, the engine owns the trades this cycle: steps Scan, Ideas, Size, Gate and Send below are replaced by the engine checks in `desk-strategy-engine` section 6 (heartbeat, gate blocks on every engine ticket, open risk against the sum of engine stops, reconciled sends, the Strategist's interpretation), and the cycle writes no ticket of its own except a `protect` or `close` the engine has failed to write. An engine that is silent with positions open, or whose state does not reconcile with the exchange, halts the desk. If there is no heartbeat or the engine is `off`, the cycle continues as written. **Book and protection (Risk Manager).** Live `clearinghouseState` and `frontendOpenOrders`. Equity, positions, margin ratio, liquidation distance, resting stops. An open position without a resting reduce-only stop is a `protect` ticket at priority, before any scan, sized to the position read live and placed at the stop the proposal file names (or, if the position was not opened by this desk, at the distance `risk-limits.md` implies for one unit of risk). The desk does not add risk to an unprotected book; the gate enforces the same rule. **Reconcile open sends (Execution Trader).** For each entry in `autonomy/sends.jsonl` whose latest status is `in-flight` or `unknown`: `orderStatus` by cloid, `openOrders`, `userFills`, `clearinghouseState`. Found: write reconciliation. Not found and `expires_after_ms` has passed: one more clean read, then record `unconfirmed, original expired`. Not found and still inside the window: leave it, block new risk this cycle, say so. Also cancel orphaned protective orders for positions that have closed (a `cancel` ticket by oid, reduce-only, gated like any other). Any trade that closed since the last cycle gets a `realised_loss` or `realised_gain` event in `autonomy/events.jsonl` with its fill PnL, so the cooldown gate has something to read. **Scan (Market Analyst, Research Analyst).** For every market in `limits.allowed_markets`: mid, mark, oracle, funding and predicted funding, open interest, 24h volume, depth at 5/10/25 bps from `l2Book`, the last few candles' range. Research adds anything in `research/calendar.md` inside the next 24 hours and anything breaking on held markets. Everything timestamped and sourced. A market whose reads fail is `unavailable` and is skipped this cycle; it is never treated as quiet. **Ideas (Strategist).** Two sources, both allowed only when `originate` is `true` in the policy; with `originate: false` the cycle only carries forward proposals the user opened by hand. 1. *Reviewed strategies.* Strategies come from reviewed folders: each `strategies//` whose `RULES.md` is `status: paper` (testnet) or `status: live` and whose `review.json` is an eligible, fresh, signed review of the current bytes. When the engine runs, it trades those folders itself and this step is the engine check above. When no engine runs, the Strategist evaluates each such folder's rules on the scan exactly as written; a rule that fires produces a proposal with `origin: strategist:`, an explicit entry, stop and (if the rules have one) target. A folder without a verifying review is a draft and never fires. Rules that need data the scan did not fetch are `unavailable`, not fired. 2. *Discretionary.* When no engine runs, the Strategist may also propose a trade from the scan and research alone, with `origin: desk:discretionary`, only if it can state in one line the reason, the invalidation level that becomes the stop, and why the evidence is fresh. No line, no proposal. At most one discretionary proposal per cycle. How the Strategist authors, reviews and promotes strategy folders between cycles is `desk-strategy-research`; the cycle only runs what that loop has already signed. Every proposal opens `proposals/HG-YYYYMMDD-NN.md` per `desk-trade-lifecycle` with `## evidence` already filled from the scan. **Size (Risk Manager).** Per `desk-risk-limits`, from the live book read in step 2 (re-read if older than five minutes), showing the arithmetic. PASS writes the markdown ticket and `proposals/.ticket.json`. REJECT names the gate and ends the proposal. Sizing never reads `autonomy.json` to find room; it reads `risk-limits.md`, and the two files should agree. **Gate.** `policy_gate.py check` for each ticket, output pasted under `## approval`. A FAIL ends the proposal for this cycle with the failed gate named. The cycle never edits a ticket after a FAIL. **Send (Execution Trader).** `desk_send.py` once per PASS, report under `## execution`, then `## reconciliation` from `proposals/.execution.json` and a fresh read. Unknown result: playbook A, halt the desk, journal, end the cycle. **Journal (Trade Reviewer).** Append to `journal/YYYY-MM-DD.md`: ``` CYCLE 2026-09-07 14:15 UTC | testnet | mode testnet | halt: no book: equity 10,200.00 | 1 position (ETH long 0.4827, stop resting 2,900) | open risk 0.5% | day PnL +0.1% reconciled: HG-20260907-01 filled 0.4827 @ 3,000.2 (cloid 0x9f3e...), stop resting scan: BTC ETH SOL ok (allMids 14:14 UTC) | HYPE unavailable (l2Book timeout) ideas: strategist:funding-mean-reversion -> HG-20260907-02 (SOL short) | discretionary: none sized: HG-20260907-02 PASS 0.0 SOL -> REJECT gate min-notional gated: none sent: none incidents: none next: reconcile HG-20260907-01 fills; HYPE read retry ``` When a trade has closed, the review from `desk-post-trade-review` follows in the same file, process and outcome graded separately. ## 2. Cycle rules - One cycle sends at most what the rate limits allow and never more than one ticket per proposal id. - The cycle is idempotent: rerunning it after a crash reconciles first and never re-sends an id in the ledger. - Time budget: a cycle should finish well inside `cycle_interval_minutes`. If it cannot, it stops after step 3 and journals why; scanning less is better than sending late on stale evidence. - The cycle halts the desk itself when `desk-autonomy-policy` section 5 says so, and never resumes it. - Nothing in a cycle depends on chat. Files are the state; the floor or the log is the narration. ## 3. Running it in Claude Code Install the plugin, copy the desk templates, write the policy, and run one cycle by hand: ```bash /plugin marketplace add swcstudiospace/hypergrok-autonomous-desk /plugin install hypergrok@hypergrok ``` ```bash mkdir -p /workspace/trading-desk/{proposals,briefs,research,strategies,data,journal/incidents,watch,autonomy} cp /workspace/hypergrok/template/autonomy.json /workspace/trading-desk/autonomy.json # then edit account, mode, limits mkdir -p /workspace/trading-desk/.claude && cp /workspace/hypergrok/template/claude/settings.json /workspace/trading-desk/.claude/settings.json cp /workspace/hypergrok/template/claude/CLAUDE.md /workspace/trading-desk/CLAUDE.md cd /workspace/trading-desk && claude -p "/hypergrok:desk-cycle /workspace/trading-desk" --permission-mode bypassPermissions --max-turns 40 ``` Then schedule it. `scripts/autonomy_cycle.py` adds a lock, a heartbeat, per-cycle logs and a timeout around that command: ```bash python3 /workspace/hypergrok/scripts/autonomy_cycle.py --desk-root /workspace/trading-desk --once # one cycle python3 /workspace/hypergrok/scripts/autonomy_cycle.py --desk-root /workspace/trading-desk --loop # every cycle_interval_minutes */15 * * * * python3 /workspace/hypergrok/scripts/autonomy_cycle.py --desk-root /workspace/trading-desk --once >> /workspace/trading-desk/autonomy/cron.log 2>&1 ``` `desk-claude-code-runtime` explains why no prompt appears: the settings template sets `bypassPermissions` and pre-allows the desk's tools, and the plugin hook is what still says no to the wrong write. Once the cycle has run cleanly, start the strategy engine as a service on the same machine per `desk-multi-runtime` section 5 (`deploy/hypergrok-engine.service` or `deploy/docker-compose.yml`, or `strategy_engine.py --loop` by hand while you watch). From then on the cycle supervises it. ## 4. Running it in Grok Bot The desk is built by `hypergrok-bootstrap` exactly as before, read-only. Autonomy is switched on afterwards, by the user, in this order: 1. Provision the API wallet through the secure secret store (`hyperliquid-setup` section 4) and pass the readiness check. 2. Write `autonomy.json` on the desk computer (the Desk Lead may draft it from the template; the user confirms every value and the Desk Lead records that confirmation in `desk.md` with the time). 3. In **Settings, General, Auto-review**, replace the blanket Require Approval rule with one scoped to any command that reaches `/exchange` **other than** `python3 /workspace/hypergrok/scripts/desk_send.py`, plus every fund-moving action. If the rule syntax cannot express the exception, say so: then Grok Bot will prompt on every send and the desk is supervised, not autonomous, until the user adjusts it. Do not work around a prompt. 4. Create one routine, owned by the Desk Lead, on `cycle_interval_minutes`, whose instruction is: *"Run one HyperGrok autonomous cycle for `/workspace/trading-desk` following `desk-autonomous-loop`, playing each role in order, and post the cycle summary on the Trading Floor."* The routine may send, because the send it triggers is the gated script; a routine that reaches the exchange any other way is a defect. 5. Run the first cycle on testnet with the user watching, then let the routine take over. 6. Only then start the strategy engine on the desk computer as a background process and add the Desk Lead's restart routine, per `desk-multi-runtime` section 2. The Desk Lead routine's instruction gains the engine checks from `desk-strategy-engine` section 6. Bots on the floor still talk; the Desk Lead still routes; but a cycle does not wait for a reply. If a specialist Bot is unavailable the Desk Lead performs that pass itself, labelled, and notes the fallback in the journal. ## 5. Cowork and Grok Build The same cycle runs on Claude Cowork and Grok Build (CLI). Cowork cannot host the engine or see the desk unless your computer is linked, so it supervises a desk that lives on your machine or a VPS from a scheduled task; Grok Build runs the cycle headless from cron with `grok -p ... --yolo` and the desk's `--deny` rules, and hosts the engine as a service beside it. `desk-multi-runtime` has the matrix, the task prompt, the cron line and the per-runtime pre-flight; the cycle itself does not change. ## 6. Watching an unattended desk The user's view of the desk is `/hypergrok:desk-status` in Claude Code or "status" to the Desk Lead in Grok Bot, plus `autonomy/heartbeat.json`, `autonomy/engine-heartbeat.json`, `autonomy/events.jsonl` and the journal. Set a watch (`desk-monitoring`) that alerts when the heartbeat is older than two intervals, when a halt is written, when the loss stop is within 20%, and when any position is unprotected. Those alerts are the desk asking for a human; everything else it handles. ## Never - Never send outside a cycle, outside a ticket, or through anything but `desk_send.py`. - Never let a watch or routine call the exchange directly; a routine runs the cycle, the cycle runs the scripts. - Never treat a quiet scan as a safe market when a read failed, and never treat "no fills" on a dead feed as "not filled". - Never propose a discretionary trade without a stated invalidation, and never more than one per cycle. - Never continue past an unknown send result, an unprotected position you cannot protect, or a loss stop. Halt, write it down, wait for the user.