Skip to content

fix(agent): count pending queued orders against the per-token budget - #24

Merged
ralyodio merged 1 commit into
profullstack:masterfrom
eltociear:fix/agent-order-budget-race
Jul 4, 2026
Merged

fix(agent): count pending queued orders against the per-token budget#24
ralyodio merged 1 commit into
profullstack:masterfrom
eltociear:fix/agent-order-budget-race

Conversation

@eltociear

Copy link
Copy Markdown
Contributor

Fixes #23.

placeAgentOrder only checked a token's remaining budget against tokenSpentThisWindow(), which sums confirmed spend from crypto_spend_ledger — a table the daemon only writes to once it actually executes a queued buy. Orders this same token already has sitting unexecuted in the agent-orders queue (source_state) were invisible to the check, so rapid back-to-back orders could each see the same stale spent figure and all pass, letting real committed spend exceed budget_usd by however many requests land before the next daemon tick. The only re-check at execution time is the engine's global account spend budget, not this token's own cap.

Fix

  • Added pendingQueue() / pendingUsdFor() helpers in agent-trade.ts.
  • placeAgentOrder: read the queue before the budget check, add this token's own pending (enqueued-but-unexecuted) usd to spent before calling checkAgentBudget. Reused the same queue read for the existing idempotency-key check (was a separate read before).
  • getAgentBudget: same adjustment, so the reported remainingUsd reflects orders already in flight, not just ledgered ones.
  • Added a test: two 80-usd orders against a 100-usd budget token, submitted back to back with the ledger still showing 0 spent — second order now correctly rejects with 402 (remainingUsd: 20) instead of both enqueuing.

No changes to the daemon side or the global spend-budget path — this only closes the per-token accounting gap on the authorize/enqueue side.

placeAgentOrder checks the token's remaining budget against
tokenSpentThisWindow(), which only reflects orders the daemon has
already executed into crypto_spend_ledger. Orders this token already
has enqueued in source_state ('agent-orders') but not yet executed are
invisible to that check. So N rapid POST /api/agent/orders requests
submitted before the daemon's next tick each see the same stale
`spent` figure and can all pass the per-token budget_usd check — the
only guard applied at execution time is the engine's global account
spend budget (see apps/daemon crypto-trade.ts executeAgentMarketBuy →
applySpendBudget), not the token's own cap. That defeats the entire
point of "Coinbase for Agents" scoped tokens: a user handing out a
$50-budget token to a semi-trusted agent should not be exposed to
$50 × N in real exchange buys just because the ledger hasn't caught
up yet.

Fix: read the pending queue before the budget check and add this
token's already-enqueued-but-unexecuted usd to `spent` before calling
checkAgentBudget, in both placeAgentOrder (enforcement) and
getAgentBudget (so the reported remainingUsd matches). Added a test
that reproduces the bypass: two 80-usd orders against a 100-usd budget
now correctly reject the second with 402 instead of enqueuing both.
@ralyodio
ralyodio merged commit 0c8ca12 into profullstack:master Jul 4, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Agent-token trading endpoint: per-token budget can be exceeded by rapid-fire orders before the queue drains

2 participants