Claude Proposes, Claude Cross-Examines

Wiring headless Claude into the trading bot's day by role — news collection, the morning meeting, cross-examination, an evening self-audit, and a different failure default for each role.

The trading bot runs the rule approach and the Claude approach side by side on a paper account and compares them. The Claude approach takes target weights from a single headless call each morning and executes them straight onto a virtual ledger. After about a month, those proposals were going into the ledger with nobody questioning them, and the news was being summarized without ever being tied to the tickers we hold.

What I set out to do

The trading bot runs the rule approach and the Claude approach as an A/B on paper trading. The Claude approach gets a target-weight proposal from a single headless claude -p call every morning and executes it on a virtual ledger, but after about a month of running it, holes showed. News was only summarized and never connected to our portfolio, and proposals went into the ledger without anyone questioning them. At a company of humans, these would be filtered out in a meeting.

So today I decided to complete the morning meeting between the morning news collection and the evening self-diagnosis that were already running. The core is the cross-examiner: separating the Claude that proposes from the Claude that attacks.

Wired into the daily schedule by role

08:30 news collection. claude -p is invoked with only --allowedTools WebSearch attached. US and Korean markets, rates and FX, risk factors, within 10 sentences total, sources cited, no investment advice. If it fails, the summary slot gets “(collection failed — no information. Judge conservatively)” verbatim. A silent blank makes the next stage optimistic.

08:50 morning meeting. Performance and risk analysis is pure computation with no LLM. There’s precedent for a single expired CLI credential halting judgment for three days, so I don’t add failure points to the judgment layer. The market analyst (headless Claude) interprets the overnight news only in terms of “the tickers we actually hold” and is forced to end with one HEADLINE: sentence on the last line. Then the strategist proposes target weights as JSON, and that proposal is handed to the cross-examiner I attached today.

You are the cross-examiner on the investment committee. Attacking this proposal is your only job. Do not propose alternatives. Do not look for reasons to agree. Find only weaknesses.

The cross-examiner’s output is JSON too: a verdict (PASS/OBJECT) and a severity of 0-3. An objection of severity 2 or higher triggers a Telegram approval request, and if there’s no approval command before the 09:05 execution, the Claude approach doesn’t trade that day. A gate whose default is “don’t.”

17:00 self-diagnosis. The day’s entire operating record (job runs, warning events, orders, virtual trades, P&L, ledger reconciliation) is scraped from the DB and dropped into the prompt whole.

Diagnose based on this record only. No exaggeration, do not speculate about anything not in the record. Only propose code and config changes, never instruct their execution.

Every call uses the same frame. Role declaration (“You are …”) + verbatim data insertion + explicit statement of constraints enforced by code + enforced JSON schema.

Where it broke: a prompt prohibition is not enforcement

Two things snagged.

First, writing “don’t” in a prompt is a request, not enforcement. So the strategist prompt states explicitly that “the system enforces this in code, so violating proposals are discarded,” and the code actually does that. If the weights sum to more than 1 or any is negative, the whole thing is discarded without correction. The reviewer that screens trading signals is stricter. Its authority is limited to approve/reject/reduce, and if the reduction ratio exceeds 1, it isn’t clipped to 1, it’s rejected. An out-of-range value isn’t noise to be patched over; it’s evidence of an attempt to increase the quantity.

Second, the failure default. Headless calls die readily from timeouts and parse failures. Make everything “reject on failure” and a single CLI outage halts everything; make everything “pass on failure” and the review in front of the money becomes decoration.

The criterion: is it on the path the money travels?

The conclusion was to set a different default per role. The reviewer, which holds the order quantity, is an unconditional REJECT on failure; after one retry, everything is rejected. The strategist on failure yields 0 signals. The cross-examiner and the market analyst, on the other hand, don’t block trading when they fail. Analysis is auxiliary and enforcement is done by the guardrail code. Instead, the failure itself is left in the activity log.

The evening self-diagnosis is the last piece of this structure. In last week’s run, based on the job execution history, both approaches’ P&L, and the ledger reconciliation, it ruled “no anomalies,” and it even noted that only its own job was RUNNING because it was the one running, diagnosing itself as “in progress, within normal range.” The picture of Claude auditing Claude’s trading records is actually running. But the diagnosis is read-only. Applying anything requires my approval in a session. Whether the cross-examiner I attached today produces objections worth having is something to watch from next week.

Lessons