Skip to content

fix(agents): refuse a plain stop that would orphan on-chain positions - #184

Open
fengtality wants to merge 1 commit into
mainfrom
fix/agents-stop-guard
Open

fix(agents): refuse a plain stop that would orphan on-chain positions#184
fengtality wants to merge 1 commit into
mainfrom
fix/agents-stop-guard

Conversation

@fengtality

Copy link
Copy Markdown
Contributor

Split out of #162 (unrelated to the LP agent content there).

Problem

engine.stop() tears down executor tracking — executors land in SYSTEM_CLEANUP — while any on-chain position stays live. Once tracking is gone, stop_executor(keep_position=false) can't reach the position, so recovery means going around the executor layer entirely.

Hit for real: stopping an LP agent with two live CLMM positions succeeded, marked both SYSTEM_CLEANUP, and left ~$25 open on-chain with no alert. Found by accident.

Fix

Only applies to agents declaring on_kill_switch: flatten_all — for them, positions are risk to close, never spot to keep. keep_all and keep_spot_close_perp are untouched, as is stopping an agent holding nothing.

  • condor/agents/shutdown.pyopen_risk(engine) reuses the winddown's own lookups so the guard and shutdown can't disagree about what "open" means.
  • condor/web/routes/agents.py_guard_stop_orphaning() raises 409 naming what's open; force=true overrides. In the all-instances branch every engine is guarded before any is stopped, so a 409 can't leave half stopped.
Before After
Plain stop w/ open risk succeeded silently 409, names the position
close_type SYSTEM_CLEANUP ×2 never reached
Operator awareness none 409 + 🚨 stranded alert

🤖 Generated with Claude Code

Stopping a strategy that still holds open positions silently stranded
them. `engine.stop()` cleans up executor tracking (executors land in
SYSTEM_CLEANUP) while the on-chain position stays live, so the normal
`stop_executor(keep_position=False)` close path can no longer reach it.
Recovery means going around the executor layer entirely.

This is only wrong for agents whose shutdown policy declares
`on_kill_switch: flatten_all` -- those are saying their positions are
risk to close, not spot to keep. For them a plain stop is almost always
a mistake; `shutdown` is the operation that winds down per shutdown.md.

Guard `/stop` for exactly that case: 409 with the open executors and
positions named, pointing at `shutdown`, with `force=true` as the
explicit override. Other policies (keep_all, keep_spot_close_perp) are
untouched, as is stopping an agent that holds nothing.

`open_risk()` reuses the winddown's own executor/position lookups so the
guard and the shutdown path cannot disagree about what "open" means.

In the all-instances branch every engine is guarded before any is
stopped, so a 409 can't leave half the instances down.

Verified against the failure it fixes. Before, stopping the LP agent
with two live positions succeeded and left both as SYSTEM_CLEANUP with
~$25 stranded on-chain and no alert. After, the same stop returns 409
naming the open position, and `shutdown` attempts the flatten and raises
its stranded-position alert when a swap fails -- no silent orphan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0154EUNSY4ocqsWPKZWgwBJn
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.

1 participant