# ParlayMarket agent platform runbook

This repository now ships the versioned agent contract and a paper-first control plane. The production contract is exposed at `/api/v1`, the shared MCP endpoint is `/api/mcp`, and the operator UI is `/agents.html`.

## What is enabled

- Native public market discovery and observed order books for Polymarket US and Kalshi.
- Fixed-point `MarketRef`, `OrderSpec`, immutable `Plan`, `ExecutionIntent`, `DelegationGrant`, `Workflow`, and `WorkflowEvent` contracts.
- REST, stateless MCP Streamable HTTP, JavaScript, Python, and JSON CLI clients backed by the same tool registry.
- Wallet owner sessions, OAuth authorization-code + PKCE, scoped client identity, exact-plan approvals, bounded paper mandates, per-owner durable reservations, version preconditions, replay-safe mutations, cursor events, signed webhooks, and dead-letter delivery.
- Cloudflare Durable Object coordinator, Workflows binding, D1 event projection, and a browser control center. Local development uses the SQLite D1 and in-memory coordinator adapter in `scripts/dev_worker.mjs`.
- Order-book-based paper simulation with fee allowances, partial fills, shared depth, stale-quote checks, and explicit dependency warnings.

## What remains deliberately blocked

Live order submission and live positions stay disabled until an isolated venue account integration has been reviewed and configured. `runtimeConfig()` fails closed even when legacy administrator credentials are present. Native venue combos and cross-venue routing are represented as non-executable data until a verified mapping and adapter capability exist. No partner authentication, custody, settlement, or production credentials are included in this branch.

The supervised execution journal in `worker/agent/execution.js` is the adapter boundary for the pilot. An adapter must provide preview, submit, lookup, and cancel operations; persist the submission intent before network I/O; return attributable evidence; and prove deduplication before any retry policy is enabled.

## Local checks

```powershell
npm run build
npm run build:worker
node scripts/build_agent_contracts.mjs
node --test tests/agents/*.test.mjs
node tests/agents/oauth.workerd.mjs
node node_modules/@playwright/test/cli.js test --config playwright.agents.config.ts
node scripts/dev_worker.mjs --port 8792
```

`npm run build:worker` writes the bundle consumed by the workerd test to
`output/agent-build/entry.js`. Wrangler resolves its output directory relative
to the Worker configuration, so the package command uses `../output/agent-build`.
The OAuth test now completes an SDK → MCP → owner approval → durable paper run
inside workerd, using isolated venue fixtures. It also checks both venue adapters.

For current public venue observations with a throwaway local wallet, run
`npm run test:agents:live` after `npm run build`. This browser test serves the
built artifact, finds a contract on each venue, inspects sources, simulates,
approves, follows completion automatically, and reloads history. Only public
venue reads leave the machine; all authentication and workflow writes stay local.

The local worker uses public-data fixtures for venue calls. It does not contact a trading endpoint. Browser tests use a fixture wallet and exercise the real Worker, coordinator, D1 schema, and UI.

## Provisioning

### Verified production topology (2026-09-07)

The custom domain is attached to the existing Cloudflare Pages project
`parlaymarket`, using Git integration on `main`. GitHub Pages is not enabled;
the old `actions/deploy-pages` workflow was failing before its build step.
The separate `parlaymarket-api-proxy` Worker owns `/api/*` and
`/.well-known/oauth-*` on the apex and `www` hosts.

Pages must use build command `bash build.sh`, output directory `output/site`,
and the repository root. Node 24 is pinned in `.node-version`. A push to `main`
starts **Release agent Worker** and the existing Pages Git integration. Pages
builds and tests its candidate, then waits up to ten minutes for the exact same
Worker commit before publication. Preview branches build without requiring their
unreleased Worker in production. **Verify Cloudflare Pages** follows a successful
Worker release and verifies the triggering commit; it does not deploy another
static host. See [Release pipeline and recovery](RELEASE_PIPELINE.md) for the
credential prerequisite and retry sequence. `404.html` disables the implicit
SPA fallback, and `_headers` prevents the zone TTL from extending module caching.
The manifest records both source hashes and complete HTML response hashes after
the existing Pages analytics snippet is inserted. JavaScript and CSS are compared
byte for byte; no scripts are stripped or arbitrary HTML changes ignored.

The production `OAUTH_KV` namespace is recorded in `worker/wrangler.toml`.
`prepare_worker_release.mjs` uses that ID unless `OAUTH_KV_ID` overrides it.
For an authorized local release, run that script followed by
`npx wrangler deploy --config worker/wrangler.release.toml`. Existing secrets
are retained. Use `wrangler d1 migrations list/apply parlaymarket-tickets
--remote --config worker/wrangler.toml` to inspect and apply only pending
migrations; never reset production storage. The agent event migration is additive.

The older incident notes below describe observations before the account-level
deployment configuration was inspected; the topology above supersedes their
assumption that GitHub Pages served the domain.

For a newly provisioned environment, an operator must configure its own approved
OAuth namespace, D1 database, durable bindings and allowed origins before release.
The existing production namespace is already recorded in `worker/wrangler.toml`;
do not create a replacement as a build repair. Hosted model access and live
trading require their own review and are not prerequisites for public paper tools.

Run a dry build with `wrangler deploy --dry-run --config worker/wrangler.toml`. Do not deploy from a development checkout. The static Pages release does **not** deploy the API Worker. Release the Worker separately through the authorized production process, using `worker/wrangler.toml` (entry point `src/entry.ts`), before publishing a frontend that depends on its routes.

The **Release agent Worker** GitHub workflow runs on `main` pushes and supports
manual retries. It uses the existing GitHub `Cloudflare` environment. Configure
`CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` there or as repository secrets;
the preflight identifies missing names without exposing values. `OAUTH_KV_ID`
is an optional environment variable override for the already provisioned namespace.
The preparation script validates the namespace and actual checkout identity,
then writes an ignored release configuration with `BUILD_COMMIT`. The workflow
validates the candidate before any production write and applies only the additive
`0010_agent_events.sql` migration, deploys the Worker and its durable bindings,
and verifies the expected Worker commit, OAuth discovery, schemas, OpenAPI,
MCP initialization and tool discovery. Both public venue journeys and the combined
paper basket are then reported separately: an upstream outage remains explicitly
degraded without blocking publication of the recovery UI. Earlier workspace
migrations must already be in place. This workflow does not provision a hosted
model or enable live trading.

After a Worker release passes, use **Retry deployment** on the Cloudflare Pages
build for that exact `main` commit if its initial wait expired. There is no
**Deploy Pages** GitHub workflow. The Pages build creates `output/site`, a public
artifact with a build identity and SHA-256 asset manifest. The build versions
HTML asset references and the application module imports together. Publish
`output/site` on the existing static host; the repository root is not the release
artifact. The post-release smoke check verifies both the commit and actual agent
asset bytes. Headers in `_headers` only apply on hosts that honor that file.

`npm run check:agent-api -- --require-workflows --require-oauth --check-markets`
is a public-read-only production check. Omitting the two requirement flags allows
explicitly unconfigured optional services in local development. A capability flag
proves a binding is present, while the isolated authenticated tests verify its
behavior; public checks do not create production owners or approval records.
Use `--report-markets --report-file output/venue-health.json` instead of
`--check-markets` for a nonblocking venue-health report. Control-plane failures
still fail the command. A degraded report does not certify the two-venue task.

Run `npm run check:backend` against production, or `node scripts/check_backend_ready.mjs --url http://127.0.0.1:8792` against the local Worker. The Pages workflow uses this gate. It requires both workspace storage readiness and the `parlaymarket-agent-v1` public capability contract; optional assistant, OAuth, and workflow provisioning may still be unavailable and are reported by the UI. A passing `/api/readiness` alone does not prove the agent API is deployed. Do not redirect agent requests to the legacy `/api/markets` catalog: it has a different venue and response contract.

## Service-loading incident (2026-09-07 UTC)

Direct unauthenticated HTTPS requests from Node confirmed that `/agents.html` redirected to `/agents` and returned 200, but `GET /api/v1/capabilities` and `POST /api/v1/tools/search_markets` (Polymarket US, empty query, limit 20) returned 404 with `{"ok":false,"error":"not_found"}`. The same symptom appeared in the Codex browser. The browser/web inspection restrictions were not the cause of the independently observed API responses.

The live `App/js/agent_center.js` matched commit `5c46a0d` byte for byte. `/api/health` and `/api/readiness` returned 200; readiness reported `workspace-v2`, build `009dea6`. The 404 body and CORS headers match the legacy Worker fallback, rather than the agent router's structured error envelope and `X-Request-Id`. The checked-in agent router already handles `/api/v1/*` before legacy prefix stripping, and the Wrangler `/api/*` route covers these URLs. This establishes a static/API deployment mismatch; the active Cloudflare deployment IDs and account configuration were not inspected. `/build-info.json` returned HTML, so it could not identify the entire static release.

The previous readiness gate checked only workspace storage and accepted this mismatch. It now rejects the live missing-agent-route response without printing raw response details. Client capability errors and timeouts settle into unavailable states with Retry service; session restoration and consent review run independently. Public discovery has its own progress/error status and Find markets retry path. No fallback market data, authentication bypass, live execution enablement, deployment, or Cloudflare configuration change is part of the repair.

The native Polymarket US catalog returned HTTP 200 directly through the local production adapter, with 20 normalized open markets and a next-page cursor. Regression checks use deterministic venue fixtures through the production Worker, and cover missing routes, non-JSON/invalid responses, network failure, timeout, recovery, public discovery, session isolation, paper simulation, and wallet authorization. Production recovery and venue access from the Cloudflare runtime still require verification after a separately authorized Worker release.

## Rollback and incident controls

Set `AGENT_NEW_RUNS_ENABLED=false` to stop new workflow reservations while retaining reconciliation and cancellation. Use the owner control center's emergency pause to stop new steps for one owner. Never delete coordinator state or event rows during an incident. Unknown submissions retain reservations until venue lookup supplies attributable evidence. Revoke the client or mandate to prevent future submissions; reconciliation remains available for already submitted orders.

## Expansion gates

Promote from paper to supervised execution only after account isolation, preview, cancellation, crash recovery, unknown-submission reconciliation, stale quote, partial-fill, out-of-order event, revocation, and aggregate-budget tests pass against the selected venue sandbox. Promote to bounded autonomy only after concurrent-owner and replay testing passes. Track time to first workflow, plan-to-approval conversion, event lag, reconciliation delay, unresolved order age, completed-workflow cost, duplicate orders, and spending-limit breaches.
