Skip to main content
POST
/
v1
/
admin
/
control-plane
/
strategy-cycles
/
*
Admin Control Plane Strategy Cycles
curl --request POST \
  --url 'https://api.glider.fi/v1/admin/control-plane/strategy-cycles/*' \
  --header 'X-API-KEY: <api-key>'
These routes are the second concrete workflow-orchestration migration seam from apps/engine into Cloudflare. The ownership split is:
  • Cloudflare agent-control-plane owns scan cadence, queueing, and durable workflow instances
  • platform-api owns the thin authenticated admin surface
  • shared services still own the canonical strategy-cycle reconciliation and child workflow dispatch path

Scope

  • Internal-only
  • Protected by ADMIN_AUTH_SECRET_TOKEN
  • Intended for apps/agent-control-plane, not browser traffic

Endpoints

POST /v1/admin/control-plane/strategy-cycles/due

Load active strategy-cycle scan candidates. Request body:
{
  "asOfIso": "2026-04-14T12:30:00.000Z",
  "limit": 100
}
Response:
{
  "asOfIso": "2026-04-14T12:30:00.000Z",
  "due": [
    {
      "nextRunAt": null,
      "ownerAddress": "0x1111111111111111111111111111111111111111",
      "portfolioId": "pf_123",
      "requestId": "strategy-cycle-tick:agt_123:2026-04-14T12:30:00.000Z",
      "strategyId": "agt_123",
      "title": "QQQ close/open"
    }
  ]
}
Notes:
  • requestId is the stable idempotency key Cloudflare should preserve when creating its own workflow instance
  • current due selection intentionally mirrors the engine scheduler’s active-strategy scan, not a narrower future matcher feed

POST /v1/admin/control-plane/strategy-cycles/tick

Execute one deterministic strategy-cycle scan through the shared strategy and workflow runtime. Request body:
{
  "asOfIso": "2026-04-14T12:30:00.000Z",
  "requestId": "strategy-cycle-tick:agt_123:2026-04-14T12:30:00.000Z",
  "strategyId": "agt_123"
}
Response:
{
  "blockedCount": 0,
  "dispatched": [],
  "dispatchedCount": 0,
  "ownerAddress": "0x1111111111111111111111111111111111111111",
  "portfolioId": "pf_123",
  "reconciledCycleCount": 0,
  "requestId": "strategy-cycle-tick:agt_123:2026-04-14T12:30:00.000Z",
  "scannedAt": "2026-04-14T12:30:00.000Z",
  "strategy": {
    "kind": "buy_close_sell_open",
    "lastRunAt": null,
    "linkedPortfolioIds": ["pf_123"],
    "linkedWorkflowIds": ["wf_buy", "wf_sell"],
    "nextRunAt": "2026-04-14T19:55:00.000Z",
    "status": "active",
    "strategyId": "agt_123",
    "title": "QQQ close/open"
  },
  "userId": "usr_123"
}
Notes:
  • userId is resolved from the canonical linked wallet address when available
  • Cloudflare uses userId plus portfolioId to mirror the tick result into durable UserAgent / PortfolioAgent state
  • shared services still own the actual strategy-cycle reconciliation and runNow(...) path during migration

Runtime Behavior

The current flow is:
  1. Cloudflare calls due
  2. Cloudflare creates StrategyCycleTickWorkflow instances with the returned ids
  3. Each Cloudflare workflow calls tick
  4. platform-api delegates to the shared strategy-cycle runtime
  5. Cloudflare mirrors the resulting tick state into durable user/portfolio actors
This moves cadence and durable workflow ownership to Cloudflare without duplicating strategy-cycle business logic in the worker.

POST /v1/admin/control-plane/strategy-cycles/notify-execution

Resolve a terminal execution back to its owning strategy cycle and forward that completion into the Cloudflare control plane. Request body:
{
  "asOfIso": "2026-04-14T12:31:00.000Z",
  "executionRequestId": "req_cycle_1",
  "operationId": "op_cycle_1"
}
Response:
{
  "asOfIso": "2026-04-14T12:31:00.000Z",
  "enqueued": true,
  "matchedExecution": {
    "operationId": "op_cycle_1",
    "runId": "run_cycle_1"
  },
  "portfolioId": "pf_123",
  "requestId": "strategy-cycle-execution:agt_123:2026-04-14T12:31:00.000Z",
  "strategyId": "agt_123",
  "workflowId": "wf_buy"
}
Notes:
  • platform-api resolves the execution to a canonical child workflow via workflow_execution_refs
  • it then resolves the owning strategy via linked child workflow ids instead of guessing from request-id conventions
  • Cloudflare prefers direct sendEvent("execution_completed") into an already waiting StrategyCycleTickWorkflow session and falls back to starting a fresh tick workflow when no active session is registered

POST /v1/admin/control-plane/strategy-cycles/recover

Replay a stale or orphaned strategy-cycle wait through the Cloudflare-owned runtime. Request body:
{
  "asOfIso": "2026-04-14T12:45:00.000Z",
  "portfolioId": "pf_123",
  "strategyId": "agt_123"
}
Response:
{
  "asOfIso": "2026-04-14T12:45:00.000Z",
  "enqueued": true,
  "portfolioId": "pf_123",
  "requestId": "strategy-cycle-recovery:agt_123:2026-04-14T12:45:00.000Z",
  "strategyId": "agt_123"
}
Notes:
  • this is an operator/admin replay surface, not a normal execution callback
  • Cloudflare recovery is intentionally narrow and safe:
    • replay an active execution_completed wait on the same workflow instance when possible
    • start a fresh authoritative tick only when no active instance is registered
    • fail closed when the active strategy-cycle workflow is not currently waiting on an event