> ## Documentation Index
> Fetch the complete documentation index at: https://docs.glider.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# Admin Control Plane Strategy Cycles

> Internal-only admin contract that lets the Cloudflare control plane load active strategy-cycle scan candidates and execute one deterministic strategy-cycle tick through the existing shared workflow facade.

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:

```json theme={null}
{
  "asOfIso": "2026-04-14T12:30:00.000Z",
  "limit": 100
}
```

Response:

```json theme={null}
{
  "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:

```json theme={null}
{
  "asOfIso": "2026-04-14T12:30:00.000Z",
  "requestId": "strategy-cycle-tick:agt_123:2026-04-14T12:30:00.000Z",
  "strategyId": "agt_123"
}
```

Response:

```json theme={null}
{
  "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:

```json theme={null}
{
  "asOfIso": "2026-04-14T12:31:00.000Z",
  "executionRequestId": "req_cycle_1",
  "operationId": "op_cycle_1"
}
```

Response:

```json theme={null}
{
  "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:

```json theme={null}
{
  "asOfIso": "2026-04-14T12:45:00.000Z",
  "portfolioId": "pf_123",
  "strategyId": "agt_123"
}
```

Response:

```json theme={null}
{
  "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
