{"slug": "i-built-an-autonomous-treasury-agent-then-let-a-code-review-bot-find-every-way", "title": "I built an autonomous treasury agent, then let a code review bot find every way it could lose money", "summary": "A developer built TreasuryForge, an autonomous treasury management agent for the WeMakeDevs × TrueFoundry Agent Harness Hackathon, using TrueForge's agent harness. The agent manages a simulated portfolio across cash, crypto, and NSE equities, with a human approval gate and a code review bot (Qodo) that caught critical bugs, including an approval bypass and a double-execution issue. The project demonstrates a safe decision loop with computed risk checks and sandboxed stress testing.", "body_md": "I just submitted **TreasuryForge** to the WeMakeDevs × TrueFoundry Agent Harness Hackathon, an autonomous agent that manages a simulated treasury across cash, crypto, and NSE equities, built entirely on [TrueForge](https://trueforge.dev), TrueFoundry's agent harness. This isn't a writeup about the idea. It's about what actually broke, what a code review bot caught before it shipped, and what I learned wiring a real approval gate into an agent loop.\n\nThe strategy is deliberately dumb. The harness around it is what has to be strong. TreasuryForge doesn't try to out-trade the market: it demonstrates a **safe decision loop**, real tool calls, computed risk checks (not the model's own guess), a sandboxed stress test when something looks risky, a hard human approval gate before anything executes, and a second agent that audits the first one's history afterward.\n\nThe hackathon's own bar for this was blunt: *a judge has to see TrueForge reaching a tool, running code in a sandbox, and stopping for a person. If it'd work as well as a chat box, change the project.*\n\nSo nothing here is custom orchestration:\n\n| What happens | TrueForge primitive |\n|---|---|\n| Agent calls a tool | A registered remote MCP server, not a function call from app code |\n| A trade pauses for a human | TrueForge's native approval checkpoint, not a custom `/approvals` endpoint |\n| Pre-trade analysis runs | TrueForge's own sandbox (bubblewrap-isolated), not a subprocess I spawned |\n| Periodic self-review |\n`create_sub_agent` , a real child thread in the session, not the main agent reasoning longer |\n\nThe wallet itself is a FastAPI + FastMCP server exposing `get_portfolio`\n\n, `check_risk_limits`\n\n, `execute_trade`\n\n, etc. TrueForge never touches the database directly: every mutation goes through MCP, gated behind the approval checkpoint and a shared secret.\n\n`get_portfolio`\n\n/ `get_transaction_log`\n\n, read-only evidence.`check_risk_limits`\n\nfor the exact trade it's considering. This is a `execute_trade`\n\ncall unconditionally, so the four risk triggers can't live in TrueForge's own config; they have to be a real tool the human can trust.`check_risk_limits`\n\nreports a breach, the agent runs exactly one Python script in TrueForge's sandbox, with no network access, applying a correlated shock (crypto −20%, equities −10%) to the fetched position values and computing the resulting drawdown.`execute_trade`\n\nindependently recomputes its own risk snapshot server-side; it doesn't trust whatever the model claims in `reason`\n\n.`user.tool_approval`\n\n.Current Threshold (5%): 3 decisions breached the limit. Alternative Threshold (7%): 0 decisions would have breached.\n\nSuggestion: relax the daily drawdown threshold. The current one flagged 3 of 20 recent trades as breaches despite portfolio equity remaining stable, while 7% still safely bounds risk below the historical 6.1% max drawdown observed.\n\nEvery PR went through [Qodo](https://qodo.ai) before merging, and it wasn't style nitpicks. A few that stuck with me:\n\n**The approval gate had a bypass.** Early on, the wallet server bound to `0.0.0.0`\n\ninstead of localhost, and the reset endpoint had no auth. Nothing stopped a direct MCP call from skipping TrueForge's checkpoint entirely, the one thing the whole project exists to guarantee.\n\n**A trade could double-execute on retry.** `execute_trade`\n\nreported failure to the caller *after* it had already committed the write. If the caller retried on that \"failure,\" it would trade twice. Nastiest kind of bug: correct in the happy path, wrong exactly when something else already went wrong.\n\n**Fixing one race condition created another.** Switching some FastAPI routes from `async def`\n\nto synchronous `def`\n\n(to stop blocking the event loop with SQLite calls) meant those routes now ran concurrently in a thread pool, which turned out to make the day-start risk-baseline rollover non-atomic. The fix for one review finding created a brand-new one, caught in the very next round. Then *that* fix had its own bug: the date was captured before acquiring the lock, so a stale thread could still overwrite a fresh baseline.\n\n**A test that didn't test what it claimed to.** A migration race-condition test looked correct but wasn't actually exercising the race. Confirmed by deliberately sabotaging the code under test and checking the test still passed (it did, which meant the test was wrong, not the code, yet).\n\n**One finding I dismissed on purpose, not by accident.** Qodo flagged that the dashboard's auth middleware fails open when no access secret is configured. True, but this project has exactly one operator and one deployment target (local, for a demo), not a production environment to fail closed in. I replied on the thread explaining why, and left it. Qodo accepted it and marked it resolved. Not every finding should turn into a fix; the point is deciding on the record instead of silently ignoring it.\n\nGemini's free tier (`gemini-flash-lite`\n\n) ran out mid-testing on a single busy day. Groq's `qwen3.8-27b`\n\nhas an 8,000 token-per-minute ceiling that's uncomfortably close to this agent's own ~4,600-token fixed per-turn overhead, fine for light use, not for rehearsing a demo repeatedly. I ended up wiring in OpenRouter as a third provider, verified two of its free models with an actual multi-turn tool-call round trip (two others that claimed tool support failed on the first real call), and made the one that held up the default. TrueForge's manifest takes a single `model.name`\n\nwith no built-in runtime fallback between providers, so this is a fixed preference order picked at setup time, not live failover.\n\nRepo: [github.com/codedpool/treasuryforge](https://github.com/codedpool/treasuryforge)\n\nIf you're building anything with a real approval gate in the loop, I'd genuinely recommend running a code review bot against every PR before you trust your own read of it. Three of the bugs above are things I was completely confident were fine.", "url": "https://wpnews.pro/news/i-built-an-autonomous-treasury-agent-then-let-a-code-review-bot-find-every-way", "canonical_source": "https://dev.to/codedpool/i-built-an-autonomous-treasury-agent-then-let-a-code-review-bot-find-every-way-it-could-lose-money-4ocf", "published_at": "2026-08-29 17:18:30+00:00", "updated_at": "2026-08-29 17:49:05.171543+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "ai-tools", "developer-tools"], "entities": ["TreasuryForge", "TrueFoundry", "TrueForge", "Qodo", "WeMakeDevs", "FastAPI", "FastMCP", "NSE"], "alternates": {"html": "https://wpnews.pro/news/i-built-an-autonomous-treasury-agent-then-let-a-code-review-bot-find-every-way", "markdown": "https://wpnews.pro/news/i-built-an-autonomous-treasury-agent-then-let-a-code-review-bot-find-every-way.md", "text": "https://wpnews.pro/news/i-built-an-autonomous-treasury-agent-then-let-a-code-review-bot-find-every-way.txt", "jsonld": "https://wpnews.pro/news/i-built-an-autonomous-treasury-agent-then-let-a-code-review-bot-find-every-way.jsonld"}}