Skip to main content

Hippodrome Local Stack Interface Contract

Hippodrome runs the control plane, e-commerce services, and selected Cloudflare Workers on one machine. The goal is production-like service boundaries with local transport and local infrastructure:

  • Python and Django services talk over localhost HTTP.
  • Cloudflare Workers run under wrangler dev.
  • AWS resources are emulated by moto.
  • Cognito and Marqo data plane behavior are emulated by fake_cognito and fake_cell.

The local stack should not bypass services for convenience. If production traffic goes through search_proxy, admin_server, or agentic_search, the local path should keep those services in the path and replace only the hosting surface that is unavailable locally.

Scenarios

--profile core --cell local

Starts fake_cognito, fake_cell, controller, and console. CONTROL_PLANE_URL_OVERRIDE points controller at fake_cell, COGNITO_* points at fake_cognito, and moto env is used when an AWS endpoint is configured.

--profile ecom --cell local

Adds admin_server, search_proxy, indexers, and exporters. Local ecom operation depends on ADMIN_SERVER_BASE_URL, MARQO_HTTP_URL, moto AWS_ENDPOINT_URL, seeded local API keys, and local S3/SQS/DDB tables.

--profile full --cell local

Adds agentic_search, admin_worker, and admin/fork helpers. Worker-to-worker service bindings are replaced by HTTP RPC over /__rpc, and agentic LLM calls can use deterministic local mocks.

--cell staging or --cell prod

Skips fake_cell and resolves real cell URLs. Worker local fallbacks may still use direct HTTP transport because Wrangler service bindings are unavailable, but target URLs come from resolved settings or cell config. Production writes still require human confirmation.

Worker Binding Substitutes

Cloudflare service bindings are not available between independently running wrangler dev processes. Hippodrome therefore uses explicit env vars to switch Workers to local HTTP equivalents.

search_proxy -> Marqo

Production uses the MARQO_WORKER binding. In Hippodrome, search_proxy runs with:

ENV=local
MARQO_HTTP_URL=http://localhost:9001

ENV=local relaxes the production requirement for MARQO_WORKER. MARQO_HTTP_URL enables the direct HTTP fetch path. The value is a local-stack capability flag; the actual request URL still comes from the resolved MarqoSettings or cell configuration, so --cell staging can still target a deployed cell.

If neither MARQO_WORKER nor MARQO_HTTP_URL is available, search_proxy fails fast with a service misconfiguration error.

Production uses the AGENTIC_SEARCH_WORKER service binding. Locally, search_proxy receives:

AGENTIC_SEARCH_HTTP_URL=http://localhost:9007
LOCAL_WORKER_RPC_ENABLED=true

When AGENTIC_SEARCH_WORKER is absent and AGENTIC_SEARCH_HTTP_URL is set, search_proxy calls:

POST http://localhost:9007/__rpc
Content-Type: application/json

{"method": "handleAgenticSearch", "args": [...]}

This is a local-only RPC shim. LOCAL_WORKER_RPC_ENABLED=true must be present on the destination Worker or the /__rpc route is not active.

agentic_search -> search_proxy

Production uses the SEARCH_PROXY_WORKER service binding. Locally, agentic_search receives:

SEARCH_PROXY_URL=http://localhost:9005
LOCAL_WORKER_RPC_ENABLED=true

When SEARCH_PROXY_WORKER is absent and both env vars are present, agentic_search builds an HTTP client that calls:

POST http://localhost:9005/__rpc
Content-Type: application/json

{"method": "handleSearch", "args": [...]}

The client exposes the same method names as the service binding so the agentic code continues to exercise search_proxy instead of calling Marqo directly.

/__rpc Contract

/__rpc exists only to emulate WorkerEntrypoint RPC between local Wrangler processes. It should not be exposed or relied on in deployed environments.

Requests use this shape:

{
"method": "handleSearch",
"args": []
}

Each Worker keeps an explicit allowlist of RPC methods. Unknown methods return 404 rather than dispatching arbitrary properties.

search_proxy wraps return values because its RPC methods can return either plain JSON values or Response objects:

{"__result": {"results": []}}

or:

{
"__response": {
"status": 200,
"headers": {"content-type": "application/json"},
"body": "{\"ok\":true}"
}
}

agentic_search returns the Response produced by the selected handler. When search_proxy needs to pass binary request bodies to agentic_search, it encodes ArrayBuffer arguments as:

{"__arrayBufferBase64": "..."}

AWS and Moto Interfaces

When Hippodrome starts moto, services that use AWS receive:

AWS_ENDPOINT_URL=http://localhost:9003
AWS_ACCESS_KEY_ID=testing
AWS_SECRET_ACCESS_KEY=testing
AWS_DEFAULT_REGION=us-east-1

Python services pass these through boto3. Workers that use AWS SDK clients must also honor AWS_ENDPOINT_URL; for example:

  • search_proxy uses it for S3/DynamoDB clients that read local settings and artifacts.
  • agentic_search uses it for DynamoDB clients that read cached query and user data tables.

Do not make local tests depend on AWS SSO, real IAM credentials, Cloudflare tokens, or Shopify credentials. In Hippodrome mode, tests needing real third party credentials should skip at fixture/config load time.

Local Agentic Behavior

Hippodrome sets:

LOCAL_AGENTIC_MOCK_ENABLED=true
GOOGLE_API_KEY=local-dev-placeholder
GROQ_API_KEY=local-dev-placeholder

Agentic flows should still enter through search_proxy and then call agentic_search. The mock flag is for deterministic local LLM-like responses; it is not a reason to bypass Worker-to-Worker RPC or the search path.

Adding New Local Interfaces

When a production interface cannot run unchanged under Hippodrome:

  1. Keep the production interface as the first choice.
  2. Add a local fallback behind an explicit env var.
  3. Prefer HTTP over localhost for cross-process calls.
  4. Gate local-only RPC endpoints with LOCAL_WORKER_RPC_ENABLED or a similarly explicit local flag.
  5. Keep method allowlists narrow.
  6. Preserve the production service path. A local fallback should replace Cloudflare hosting or AWS infrastructure, not skip application services.
  7. Add or update Hippodrome smoke tests for the interface and document the env vars here.

If a required production binding and its local fallback are both absent, fail fast with a clear service misconfiguration error.