Águia fleet · EC2 13.49.198.195 · 23 Aug 2026

Fleet & control-plane audit

Six parallel auditors across cost, effectiveness, reliability, orchestration, security and the control plane. Every finding re-checked by an adversarial verifier whose job was to refute it. 118 raised, 11 refuted and dropped, 107 stand.

107verified findings
8fixed and deployed today
3remote-code-execution paths closed
94.2%of runs were silently losing your instructions

Verdict

The money pipeline is genuinely good — idempotent per-run ledger, calibrated pricing, a working drill-down. Everything that reports health was fabricated, and everything that claims to learn is dead. The fleet does publish real work while three named learning loops, four monitors and two nav pages produce nothing at all.

The most damaging defect nobody had filed: on 94.2% of dispatches the fleet assembled your standing corrections under a header reading “hard rules — these override your defaults”, and then deleted them from the middle of the prompt before the model read a word. That is the mechanical explanation for the “reads like marketing” corrections you keep having to repeat.

Shipped today

All eight are live on the box and functionally verified — not proposed.

01

New agents now appear on brunopessoa.com

Your reported bug. acp-agents-sync.sh only ever pulled down a slug→id map; nothing on EC2 ever called POST /api/agents, so an agent directory created on the box could never reach the dashboard without hand-written SQL. Replaced with bin/acp-agents-reconcile.py (every 15 min): the filesystem is the source of truth, it upserts what is missing, corrects drift and flags orphans.

7 agents were invisible: bem-te-vi, claudinha, condor, el-professor, fti-sentinel, jastrzab, synthesizer End-to-end test: new agent dir → detected and registered on the next run.
02

Liveness stopped lying

Two separate scripts POSTed status=online for every agent every 5 minutes. They stamped last_heartbeat, stamped last_activity_at and cleared current_task — so the dashboard could not show a dead agent even in principle, and “what is this agent doing” was wiped four times an hour. Both disabled.

Before: 32/32 green, including 4 ghost records with no directory on the box.
03

Health is now measured against each agent's own schedule

Status came from a flat last_heartbeat > NOW() - 10 minutes. Fleet cadences run from per-minute watchdogs to the weekly synthesizer, so one window cannot describe them — which is exactly why the fake heartbeat existed. Agents now carry expected_cadence_minutes read off the crontab; API and alerter both judge against it. offline finally means something is wrong.

online = within 1.5× cadence · stale = overdue · offline = >3× overdue · idle = no cron, quiet by design Validated against a throwaway Postgres across 12 edge cases before deploy.
04

Alerting no longer pages you for agents that are simply between runs

api/alerting.py used the same flat 10-minute rule, which is what drove the fake heartbeats in the first place. It now uses the real cadence, skips on-demand agents entirely, and names the delay instead of printing a bare timestamp.

05

CRM reply responder — remote code execution closed

A prospect's raw email body was interpolated unfenced into a tool-capable Claude process, on a */10 cron, with AUTO_REPLY_ENABLED=1 and no human gate. Anyone who ever received a Cultura Builder outreach email was the attacker set. Tools removed, reply fenced as untrusted data.

Note: removing --permission-mode alone is NOT the fix. A `claude -p` call with no permission flag still executes Bash — proven on the box. The control is an explicit --disallowed-tools list.
06

WhatsApp webhook — unauthenticated internet to root, closed

The signature check read the header with an empty default and only rejected a wrong one, so omitting it entirely was a free pass. Everything downstream trusted key.fromMe as your identity — the code called it “unspoofable” — and fromMe unlocked both [WIKI] capture (writes into the brain every agent then executes) and [[BUILD]] (launches a builder with full permissions). The attacker supplies the body, so the attacker supplies fromMe.

Fixed without risking your ingest: unsigned requests are still accepted as messages, but fromMe only confers authority on a signed request, and every unsigned attempt is logged. Worst case is that WhatsApp capture degrades and says so — not a silent outage.

Attack replayed after the fix: REFUSED [WIKI] capture: fromMe on an UNSIGNED request live brain: clean — nothing written
07

llm-dispatch hardened

An internet-adjacent bearer-gated endpoint reachable from every Coolify container, passing neither a permission mode nor a tool policy — i.e. arbitrary shell as ubuntu, which has passwordless sudo. Tools removed, bearer comparison made constant-time, and the prompt moved off argv (it was sitting on the 128 KB E2BIG cliff that caused the July outage).

FTI's contract unchanged and re-tested: {"text": "PONG", "model": "claude-haiku-4-5-20251001"}
08

Season-desk daemon fan-out capped

The scanner deduped per fixture but capped nothing, so concurrency tracked the football calendar rather than the box: 15 daemons, ~1.4 GB, on a host with 3.4 MB of swap free. Now capped at 6; over-cap fixtures are logged by name and retried next scan, so nothing is dropped. Checked first that no shadow position was open.

The one that matters most

Your standing corrections were being assembled, then deleted

context-budget.py kept the first 70% and last 30% of the assembled prompt and threw away the middle. Because dispatch.sh assembles sections in a fixed order, the hole landed in the same place every time. For falcão — the agent that publishes in your name — the deleted middle averaged ~41 KB and swallowed, in order: the tail of Wiki Context, all of your standing corrections, all of Recent Memory, all of the cross-agent briefing, and about half the voice guide.

Replaced with priority-aware shedding: drop the cheapest sections first, never the task or your rules, and state inline what was dropped. Same prompt, same budget, measured:

Before — blind middle cut
Your standing rulesDELETED
Recent memoryDELETED
Current taskkept
Wiki reference (35 KB)kept
After — priority-aware
Your standing rulesKEPT
Recent memorykept
Current taskkept
Wiki reference (35 KB)shed first

Confirmed live by asking falcão what it could actually see. It answered yes, then quoted one of your real corrections back verbatim — a Portuguese-grammar note from 13 July that it had never been able to read before.

Still yours to decide: dispatch.sh injects only tail -60 of LESSONS.md, so older corrections are cut at source by design. And LESSONS.md exists for just 2 of 29 agents — the mechanism that makes your corrections stick is essentially unused.

Theater — activity without output

These consume dispatch slots, quota and attention, and produce nothing external.

SystemEvidenceVerdict
carcaraPaper mode day 130; eval date passed 100 days ago; 34 consecutive scans, 0 deployments; all four playbook strategies falsified by the agent itselfPARK
fti-intern92 NO TRADE verdicts in 13 days, zero executions all month; its own doctrine rejects longs in codeGATE IN CODE
cbuilder“0 inbound today (streak: 126 consecutive days)”. Only required output is a memory log.KILL
content-desk31 consecutive days of opus-generated briefs; deliveries succeeded: 0FIX SEND
beija-florPT edition has delivered nothing since 23 Jul — and it has 15 real external subscribersFIX
falcão health auditRED on 243 of 243 readings since 28 AprREBUILD
outreach brain1,166 emails, 760 performance records, no reply field in the schema; 0 proposals applied every SundayFIX LOOP
scorecards28 files a night; INJECT_SCORECARD is set nowhere in the treeWIRE UP
skills marketplaceLive authenticated endpoint serving invented sellers and prices from a hand-written seedDELETE
Logs / Inbox navNewest log row 23 Mar; newest conversation 5 Mar; 32 dead buttonsDELETE

Root causes

The 107 findings reduce to eight structural defects. Fixing symptoms without these changes nothing.

What to do next

  1. Rotate six committed secrets

    WASender session key, PAT and webhook secret, WIKI_BEARER, the Telegram bot token and the fallback API key are committed at HEAD and pushed nightly to GitHub — hash-verified identical to the live values. The repo is private, so rotation is sufficient. Do not rewrite history: that repo is the nightly backup of 9.5 GB of agent state.

  2. Bind Cloudflare Access to two open front doors

    ssh.brunopessoa.com answers with no Access application in front of it — an SSH listener that bypasses the AWS security group and blinds fail2ban. coolify.brunopessoa.com, the deploy control plane for the whole estate, is behind a password form alone.

  3. Give the control plane a real error signal

    Recompute the error count from agent_run, or post failures from the dispatch failure branch. Exclude clean no-trade cycles and exit 75/76 first, or backfilling 317 rows will page you on routine behaviour.

  4. Back up what exists in exactly one place

    /home/ubuntu/clawd (1.7 GB of trading and season-desk state) and the 452-line crontab that defines 173 jobs are backed up nowhere. Scrub the crontab before committing it — one line contains a bearer token.

  5. Fix the llm-dispatch cache inversion

    The highest-certainty saving in the report and a one-line change: the FTI worker polls every 6–8 minutes against a 5-minute cache TTL, so every cache write expires before it is ever read. Writes cost 4.4× what reads return.

  6. Roll tool policies across the remaining call sites, in audit mode

    Do not blanket-ban Bash — wiki-remember.sh is the knowledge-capture path and every agent writes memory files. Log what would have been denied for a week, then enforce. Start with the five pure-research agents.

  7. Make output a first-class field

    Today an agent that publishes three posts a day and one that has produced nothing since April look identical: both green, both zero errors. Cost per delivered artifact is the headline that surfaces eight of these findings by itself — and it is the gate for every kill/park decision below.

On cost

The honest answer is not the one the numbers first suggest.

Every dollar figure in the per-agent ledger is notional. api_cost is 0.0 on all 32 agents; there is no API key on the box. Auth is the flat $200/mo Max plan, so “$4,038/mo of fleet spend” is list-price equivalence, not a bill. Real cash is $526.74/mo across 14 items, and only Railway ($115) and EC2 ($71.74) are large — neither currently verifiable, because Cost Explorer is disabled and the Railway credential is dead.

Verified cash savings available today: about $3/mo. That is the finding. Everything else needs measurement only you can unlock.

The scarce resource is not the card — it is the shared 5-hour usage window. The fleet has already lost 37 hours to a quota blackout, and a second full fleet-day on 22 Aug because the backoff breaker's pattern does not match the phrase “you've hit your weekly limit”. Recovering roughly 15–22% of that window is worth more than any line-item saving, and the truncation fix alone recovers ~41 KB per falcão run that you were paying for and discarding.

Needs you personally