1. Bot Identity
| Layer | Execution Execution |
|---|
| Bot class | Execution Utility |
|---|
| Authority | Reshape |
|---|
| Status | PLANNED |
|---|
| Readiness | Spec started |
|---|
| Runs before | FillQualityAnalyzer, PartialFillHandler, CancelReplaceOptimizer |
|---|
| Runs after | SmartRouter (order signed and submitted to CLOB V2) |
|---|
| Applies to | Every live order from submission through terminal state (FILLED, CANCELLED, EXPIRED) |
|---|
| Default mode | shadow_only |
|---|
| User-visible | yes |
|---|
| Developer owner | Polytraders core — Execution pod |
|---|
Operational profile
| Modes supported | quarantine |
|---|
2. Purpose
OrderLifecycleManager is the single source of truth for every order's state machine, tracking transitions from intent through terminal state. It reconciles local state against the CLOB V2 user-state endpoint, detects stuck-in-flight and orphaned orders, and emits ExecutionReports on every transition.
3. Why This Bot Matters
Order state out of sync
Strategy layer acts on stale position data, potentially doubling into a filled position or missing a cancellation.
Stuck-in-flight order not detected
An order that never received an ack occupies position budget indefinitely, blocking new orders.
Orphaned ack not cancelled
A resting order with no owning strategy leaks capital and may fill at an unintended price.
No worked examples on this bot yet. Worked examples are optional but strongly recommended — they turn an abstract failure mode into something a developer can verify in a fixture.
6. Parameter Guide
| Parameter | Default | Warning | Hard | What it controls |
|---|
| stuck_order_timeout_s | 30 | 60 | 120 | Seconds after submission with no ack before an order is considered stuck-in-flight. |
| reconcile_interval_s | 10 | 30 | 60 | How often to poll clob_auth for the authoritative user-order list and diff against local state. |
| auto_cancel_orphans | True | — | — | Automatically cancel any order found during reconcile that has no owning strategy record. |
| publish_audit_log | True | — | — | Emit a full state-transition audit log entry (with builder_code) to the WAL on every order lifecycle event. |
7. Detailed Parameter Instructions
stuck_order_timeout_s
What it means
Seconds after submission with no ack before an order is considered stuck-in-flight.
Default
{ "stuck_order_timeout_s": 30 }
Why this default matters
30s covers normal CLOB latency spikes; beyond 60s indicates a system issue.
Threshold logic
| Condition | Action |
|---|
| age_since_submit_s <= 30 | Normal — await ack |
| 30 < age_since_submit_s <= 60 | WARN — ORDER_STUCK_WARN emitted |
| age_since_submit_s > 60 | HARD_REJECT — cancel and emit ORDER_STUCK_HARD |
Developer check
if now_ms() - order.submit_ms > params.stuck_order_timeout_s * 1000: emit(ORDER_STUCK)
User-facing English
An order took too long to be acknowledged by the exchange and was cancelled.
reconcile_interval_s
What it means
How often to poll clob_auth for the authoritative user-order list and diff against local state.
Default
{ "reconcile_interval_s": 10 }
Why this default matters
10s keeps state drift below one reconcile cycle; polling more often wastes rate-limit budget.
Threshold logic
| Condition | Action |
|---|
| interval <= 10s | Normal reconcile cadence |
| interval > 30s | WARN — reconciliation lag risk |
| interval > 60s | Reject config — PARAMETER_CHANGE_REQUIRES_APPROVAL |
Developer check
assert params.reconcile_interval_s <= params.hard
User-facing English
Order status is updated from the exchange every few seconds.
auto_cancel_orphans
What it means
Automatically cancel any order found during reconcile that has no owning strategy record.
Default
{ "auto_cancel_orphans": true }
Why this default matters
Orphaned orders must be cancelled to reclaim position budget; leaving them risks unintended fills.
Threshold logic
| Condition | Action |
|---|
| orphan detected AND auto_cancel_orphans=true | Cancel order; emit ORDER_ORPHAN_CANCELLED |
| orphan detected AND auto_cancel_orphans=false | WARN only — operator must cancel manually |
Developer check
if orphan and params.auto_cancel_orphans: clob_auth.DELETE('/order/' + order_id)
User-facing English
An unrecognised order was found and cancelled to protect your account.
publish_audit_log
What it means
Emit a full state-transition audit log entry (with builder_code) to the WAL on every order lifecycle event.
Default
{ "publish_audit_log": true }
Why this default matters
Audit logs are required for 7-year retention compliance; disabling is only allowed in isolated test environments.
Threshold logic
| Condition | Action |
|---|
| publish_audit_log=true | Every transition emitted to WAL with trace_id and builder_code |
| publish_audit_log=false | WARN — audit log disabled; only permitted in test mode |
Developer check
if params.publish_audit_log: wal.append(transition_record)
User-facing English
Every change to your order is logged for transparency and record-keeping.
8. Default Configuration
{
"bot_id": "exec.orderlifecyclemanager",
"version": "0.1.0",
"mode": "shadow_only",
"defaults": {
"stuck_order_timeout_s": 30,
"reconcile_interval_s": 10,
"auto_cancel_orphans": true,
"publish_audit_log": true
},
"locked": {
"stuck_order_timeout_s": {
"max": 120
},
"reconcile_interval_s": {
"max": 60
}
}
}
9. Implementation Flow
- On receipt of a signed order from SmartRouter, create an order state record: {order_id, market_id, side, price, size_usd, status=PENDING_ACK, submit_ms, builder_code}.
- Subscribe to ws_user feed for fill, cancel, and expire events on this order.
- On CLOB V2 ack received: transition status PENDING_ACK → OPEN; emit ExecutionReport(status=OPEN).
- On partial fill event: update filled_usd, remaining_usd; emit ExecutionReport(status=PARTIAL); forward to PartialFillHandler.
- On full fill event: transition status → FILLED; emit ExecutionReport(status=FILLED); forward to FillQualityAnalyzer.
- On cancel/expire event: transition status → CANCELLED or EXPIRED; emit ExecutionReport.
- Every reconcile_interval_s: fetch clob_auth /orders/open; diff against local state; detect orphans and stuck orders.
- On stuck order (age > stuck_order_timeout_s): cancel via clob_auth DELETE /order/{id}; emit ORDER_STUCK reason code.
- On orphan detected and auto_cancel_orphans=true: cancel via clob_auth; emit ORDER_ORPHAN_CANCELLED.
- On KillSwitch active: cancel all open orders; emit KILL_SWITCH_ACTIVE for each; halt further processing.
10. Reference Implementation
Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output. IF/THEN/ELSE = decision. Translate directly to TypeScript, Python, Go, or Rust.
FUNCTION manageLifecycle(order):
// 1. Initialise state
state = {order_id: order.id, status: 'PENDING_ACK',
submit_ms: now_ms(), filled_usd: 0,
builder_code: order.builder_code}
store.put(order.id, state)
// 2. Subscribe to ws_user events
ws_user.subscribe('order_update', order.market_id, order.id)
// 3. Process ws_user events
ON ws_user EVENT e WHERE e.order_id == order.id:
IF e.type == 'ACK':
state.status = 'OPEN'
ELIF e.type == 'PARTIAL_FILL':
state.filled_usd += e.fill_usd
state.status = 'PARTIAL'
FORWARD TO partialfillhandler(state)
ELIF e.type == 'FILL':
state.status = 'FILLED'
FORWARD TO fillqualityanalyzer(state)
ELIF e.type IN ('CANCEL', 'EXPIRE'):
state.status = e.type
EMIT ExecutionReport(state)
// 4. Reconcile loop (every reconcile_interval_s)
EVERY params.reconcile_interval_s:
live = FETCH clob_auth.GET('/orders/open')
FOR order_id IN store.all_open():
IF order_id NOT IN live AND store.status != 'FILLED':
state.status = 'RECONCILE_DISCREPANCY'
EMIT ExecutionReport(state, WARN)
FOR order_id IN live:
IF order_id NOT IN store:
IF params.auto_cancel_orphans:
clob_auth.DELETE('/order/' + order_id)
EMIT ExecutionReport(ORDER_ORPHAN_CANCELLED)
// 5. Stuck-order check
IF state.status == 'PENDING_ACK':
age_s = (now_ms() - state.submit_ms) / 1000
IF age_s > params.stuck_order_timeout_s:
clob_auth.DELETE('/order/' + state.order_id)
EMIT ExecutionReport(state, HARD_REJECT, ORDER_STUCK)
SDK calls used
ws_user.subscribe('order_update', market_id, order_id)clob_auth.GET('/orders/open')clob_auth.DELETE('/order/' + order_id)
Complexity: O(N) per reconcile where N = open orders
11. Wire Examples
Input — what arrives on the wire
ws_user partial fill event — ws_user
{
"order_id": "0xaaaa1111bbbb2222cccc3333dddd4444eeee5555ffff6666aaaa7777bbbb8888",
"type": "PARTIAL_FILL",
"fill_usd": 150,
"fill_price": 0.62,
"fill_ts_ms": 1746770000000,
"collateral": "pUSD"
}
Output — what the bot emits
ExecutionReport — PARTIAL status
{
"report_id": "rep_1a2b3c4d5e6f7a8b",
"trace_id": "trc_9f8e7d6c5b4a3f2e",
"bot_id": "exec.orderlifecyclemanager",
"order_id": "0xaaaa1111bbbb2222cccc3333dddd4444eeee5555ffff6666aaaa7777bbbb8888",
"status": "PARTIAL",
"filled_usd": 150,
"remaining_usd": 300,
"collateral": "pUSD",
"builder_code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
"evaluated_at_ms": 1746770000000
}
12. Decision Logic
APPROVE
Order lifecycle event is processed normally; state transitions recorded and ExecutionReport emitted.
RESHAPE_REQUIRED
Reconciliation detects a state discrepancy; local state overwritten with exchange-authoritative state.
REJECT
KillSwitch active or order stuck beyond hard threshold — cancel order, emit HARD_REJECT reason code.
WARNING_ONLY
Order approaching stuck threshold or reconcile interval exceeds warning level — emit WARN, continue.
13. Standard Decision Output
This bot returns a ExecutionReport object. See ExecutionReport schema.
{
"report_id": "rep_1a2b3c4d5e6f7a8b",
"trace_id": "trc_9f8e7d6c5b4a3f2e",
"bot_id": "exec.orderlifecyclemanager",
"order_id": "0xaaaa1111bbbb2222cccc3333dddd4444eeee5555ffff6666aaaa7777bbbb8888",
"market_id": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
"side": "BUY",
"status": "PARTIAL",
"filled_usd": 150,
"remaining_usd": 300,
"collateral": "pUSD",
"builder_code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
"eip712_domain_version": "2",
"evaluated_at_ms": 1746770000000
}
14. Reason Codes
| Code | Severity | Meaning | Action | User-facing message |
|---|
ORDER_LIFECYCLE_TRANSITION | INFO | Normal state transition recorded. | Emit ExecutionReport; no intervention required. | |
ORDER_STUCK | HARD_REJECT | Order has been PENDING_ACK beyond stuck_order_timeout_s. | Cancel order via clob_auth; emit ExecutionReport. | Your order was cancelled because the exchange did not acknowledge it in time. |
ORDER_ORPHAN_CANCELLED | WARN | Order found during reconcile with no owning strategy record. | Cancel order; log warning. | An unrecognised order on your account was cancelled. |
KILL_SWITCH_ACTIVE | HARD_REJECT | Global kill switch active; cancel all open orders. | Cancel all open orders; halt processing. | Trading is currently paused. |
RECONCILE_DISCREPANCY | WARN | Local state does not match exchange state after reconcile poll. | Overwrite local state with exchange state; emit WARN. | |
15. Metrics & Logs
Metrics emitted
| Metric | Type | Unit | Labels | Meaning |
|---|
polytraders_exec_orderlifecyclemanager_transitions_total | counter | count | status | Total order state transitions by terminal or intermediate status. |
polytraders_exec_orderlifecyclemanager_stuck_orders_total | counter | count | | Total orders cancelled due to stuck-in-flight timeout. |
polytraders_exec_orderlifecyclemanager_reconcile_discrepancies_total | counter | count | | Total reconcile cycles that found a state discrepancy. |
polytraders_exec_orderlifecyclemanager_open_orders | gauge | count | | Current number of open orders tracked by the state machine. |
Alerts
| Alert | Condition | Severity | Runbook |
|---|
OLMStuckOrderRate | rate(polytraders_exec_orderlifecyclemanager_stuck_orders_total[5m]) > 0.1 | P2 | #runbook-olm-stuck-orders |
OLMReconcileDiscrepancy | rate(polytraders_exec_orderlifecyclemanager_reconcile_discrepancies_total[5m]) > 0.05 | P1 | #runbook-olm-reconcile |
16. Developer Reporting
{
"report_id": "rep_1a2b3c4d5e6f7a8b",
"order_id": "0xaaaa1111bbbb2222cccc3333dddd4444eeee5555ffff6666aaaa7777bbbb8888",
"status_from": "OPEN",
"status_to": "PARTIAL",
"filled_usd": 150,
"remaining_usd": 300,
"reconcile_diff": null,
"stuck_age_s": 0,
"orphan": false,
"builder_code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
"evaluated_at_ms": 1746770000000
}
17. Plain-English Reporting
| Situation | User-facing explanation |
|---|
| Order partially filled | Part of your order was filled. The remaining portion is still open in the market. |
| Order stuck — cancelled | Your order did not receive a response from the exchange within the expected time and was cancelled for safety. |
| Orphaned order cancelled | An unrecognised order was found on your account and cancelled to protect your balance. |
18. Failure-Mode Block
| main_failure_mode | ws_user feed disconnects silently, causing fill events to be missed and local state to diverge from exchange state until the next reconcile poll. |
|---|
| false_positive_risk | Declaring an order stuck during a legitimate CLOB latency spike, causing a premature cancel and missed fill. |
|---|
| false_negative_risk | Reconcile interval too long means orphaned orders linger and consume position budget for up to reconcile_interval_s seconds. |
|---|
| safe_fallback | If ws_user feed is unavailable, fall back to polling clob_auth /orders every reconcile_interval_s. If clob_auth is unreachable, hold state and emit WARN; escalate to KillSwitch if unreachable > 60s. |
|---|
| required_dependencies | ws_user fill/cancel/expire feed, clob_auth /orders/open endpoint, KillSwitch active flag |
|---|
19. Failure-Injection Recipes
| Scenario | How to inject | Expected behaviour | Recovery |
|---|
WS_USER_DISCONNECT | Kill ws_user WebSocket connection | | WebSocket reconnects; event replay from last seq_id |
STUCK_ORDER_TIMEOUT | Block CLOB ack for target order for 90s | | Automatic; next order proceeds normally |
KILL_SWITCH_ON | Set killswitch.active=true with 3 open orders | | Manual KillSwitch reset required |
20. State & Persistence
Cold-start recovery
On restart, re-read open order state from clob_auth and rebuild local map.
21. Concurrency & Idempotency
| Aspect | Specification |
|---|
| Execution model | event-driven single-threaded loop per order |
| Max in-flight | 200 |
| Idempotency key | order_id + event_type + event_ts_ms |
| Per-call timeout (ms) | 500 |
| Backpressure strategy | shed oldest pending reconcile if queue > 200 |
| Locking / mutual exclusion | per-order_id mutex for state writes |
22. Dependencies
Depends on (must run first)
Emits to (downstream consumers)
Used by (auto-aggregated)
2.11 2.15 2.7 2.8 6.16 4.16 1.19 5.9
External services
| Service | Endpoint | SLA assumed | On failure |
|---|
| CLOB V2 auth API | https://clob.polymarket.com | 99.95% / 200ms p99 | Fall back to ws_user-only state; escalate if unreachable > 60s. |
| WS user feed | wss://ws-subscriptions-clob.polymarket.com/ws/user | best-effort | Fall back to REST reconcile polling. |
23. Security Surfaces
Abuse vectors considered
- Injecting a fake FILL event to prematurely close an order and suppress further fills
- Flooding the reconcile endpoint to exhaust rate limits and prevent orphan detection
Mitigations
- ws_user event signatures validated against CLOB V2 HMAC; unsigned events discarded
- Reconcile rate-limit budget tracked; if exhausted, local state is trusted and WARN emitted
24. Polymarket V2 Compatibility
| Aspect | Value |
|---|
| CLOB version | v2 |
| Collateral asset | pUSD |
| EIP-712 Exchange domain version | 2 |
| Aware of builderCode field | yes |
| Aware of negative-risk markets | no |
| Multi-chain ready | no |
| SDK used | py-clob-client-v2 |
| Settlement contract | CTFExchangeV2 |
| Notes | Every ExecutionReport includes builder_code (bytes32) from the originating signed order for builder attribution on all lifecycle transitions. |
API surfaces declared
clob_authws_userinternal
Networks supported
polygon
25. Versioning & Migration
| Field | Value |
|---|
| spec | 2.0.0 |
| implementation | 0.1.0 |
| schema | 2 |
| released | None |
| planned_release | Q4-2026 |
Migration history
| Date | From | To | Reason | Action taken |
|---|
| 2026-04-28 | n/a | v2-spec | Spec drafted post-CLOB-V2 cutover; bot not yet implemented | Designed against V2 schema (pUSD, builder codes, V2 EIP-712 domain) |
26. Acceptance Tests
Unit Tests
| Test | Setup | Expected result |
|---|
| PENDING_ACK → OPEN transition on ack | Submit order; inject ack event | status=OPEN, ExecutionReport emitted |
| OPEN → PARTIAL on partial fill | Inject partial fill event; filled=150, size=450 | remaining_usd=300, status=PARTIAL |
| Stuck order cancelled after timeout | age_since_submit=90s, stuck_order_timeout_s=30 | cancel issued, ORDER_STUCK emitted |
| Orphan auto-cancelled when auto_cancel_orphans=true | Reconcile finds order_id with no local record | DELETE /order/{id} issued, ORDER_ORPHAN_CANCELLED emitted |
Integration Tests
| Test | Expected result |
|---|
| Full lifecycle: submit → ack → partial fill → full fill | All transitions recorded; final status=FILLED; ExecutionReport emitted to polytraders.reports.execution |
| ws_user feed disconnect → reconcile fallback → state restored | After reconnect, state matches clob_auth; no duplicate events emitted |
Property Tests
| Property | Required behaviour |
|---|
| Order state always moves forward (no regression from FILLED/CANCELLED to OPEN) | Always true |
| builder_code present on every emitted ExecutionReport | Always true |
27. Operational Runbook
OLM incidents are usually ws_user feed disconnects causing reconcile lag, or a burst of stuck orders during CLOB latency spikes.
On-call actions
| Alert | First step | Diagnosis | Mitigation | Escalate to |
|---|
OLMStuckOrderRate | Check CLOB latency dashboard; if elevated, extend stuck_order_timeout_s temporarily. | | | Exec pod lead if stuck rate > 0.5/min sustained |
OLMReconcileDiscrepancy | Compare local order state dump with clob_auth /orders/open; identify which orders diverged. | | | Exec pod lead + infra if discrepancy persists > 10 min |
Manual overrides
polytraders bot cancel-order exec.orderlifecyclemanager --order <order_id> — Manual intervention required to cancel a stuck or orphaned order.
Healthcheck
GET /internal/health/orderlifecyclemanager → green if ws_user feed connected, clob_auth reachable, open_orders gauge stable, reconcile running within interval; red if ws_user disconnected > 30s, clob_auth unreachable, stuck_orders_total spiking
29. Developer Checklist
Ready-to-ship score: 27/27 sections complete · 100%
| Requirement | Status |
|---|
| Purpose defined | ✓ done |
| Required inputs listed | ✓ done |
| Parameters defined | ✓ done |
| Defaults defined | ✓ done |
| Warning thresholds defined | ✓ done |
| Hard thresholds defined | ✓ done |
| Safe fallback defined | ✓ done |
| Structured output defined | ✓ done |
| Developer log defined | ✓ done |
| Plain-English explanation | ✓ done |
| Unit tests defined | ✓ done |
| Integration tests defined | ✓ done |
| Property tests defined | ✓ done |
| Failure-mode block complete | ✓ done |
| Reference implementation pseudocode | ✓ done |
| Wire examples (input + output) | ✓ done |
| Reason codes listed | ✓ done |
| Metrics & logs defined | ✓ done |
| State & persistence defined | ✓ done |
| Concurrency & idempotency defined | ✓ done |
| Dependencies declared | ✓ done |
| Security surfaces declared | ✓ done |
| Polymarket V2 compatibility declared | ✓ done |
| Version & migration history declared | ✓ done |
| Operational runbook defined | ✓ done |
| Promotion gates defined | ✓ done |
| Failure-injection recipes defined | ✓ done |