architecture
How Tidegap is built.
A small, deliberately scoped system. One scheduler. One MCP surface. Grid feeds in front, provider adapters behind, carbon receipts in between.
System diagram
Where each component sits and how the data flows.
The MCP server is a thin protocol surface in front of the scheduler. The scheduler core does the work and is the same implementation used by the in-process TypeScript library and the Python port (same algorithms, same receipt schema, two host languages).
Data sources
Grid intensity feeds come from government / TSO authorities — the same sources that ISO/RTO grid operators and DOE energy-usage studies cite. Tidegap does no data collection of its own; it routes dispatch decisions through public-sector telemetry.
- U.S. Energy Information Administration (EIA). Hourly carbon-intensity for the six major US ISOs: CAISO, ERCOT, ISO-NE, PJM, NY-ISO, MISO. Free, key-required.
- UK National Grid ESO Carbon Intensity API. Key-less. Always live for GB region. The reference for UK demand-response programs.
- ENTSO-E Transparency Platform. European zones (FR, DE, ES, IT, NL). Operated by European Network of Transmission System Operators for Electricity. Free, key-required.
- Electricity Maps. Universal fallback for any other zone. Free tier sufficient for a single-user dashboard.
Every per-task carbon receipt carries the exact intensity value (and source identifier) used at scoring time, so the audit trail is reproducible — operators can later verify any individual dispatch against the public data feed it relied on.
Data flow, end to end
- Ingest. A task arrives via
defer(task, deadline)(library) orschedule_task(prompt, deadline)(MCP). The task body is captured (closure, JSON-serialized request, or a provider-adapter call descriptor) and persisted to SQLite with a UUID task_id. For non-committal planning — when the agent wants to see when the cleanest window falls before deciding whether to commit — there'srecommend_window(deadline, region), which returns the recommended window, top-3 alternatives, and a savings-vs-now number without ever touching the queue. - Validate. Deadline must parse as ISO-8601 and be in the future. If not, the call fails fast with
InvalidDeadlineError. A taskId, if caller-supplied, must be unique. - Forecast. The scheduler asks the configured grid feed for the next N hours of carbon intensity for the task's region. With an Electricity Maps API key, this hits the live API (5-second timeout). Without one, the feed falls back to a deterministic UTC-based sinusoidal mock so the whole stack still runs end-to-end.
- Score. Each hour inside the deadline window is scored as grams CO₂e = per-model Wh/token × token counts × PUE 1.15 × hour intensity. v0.10 replaced the v0.1-v0.9 flat
0.0015 kWh/taskplaceholder with a 37-model coefficient table cited from Patterson 2021, Luccioni 2024, and the Hugging Face AI Energy Score; v0.11 then Ed25519-signs the resulting receipt so any consumer can verify offline that the numbers haven’t been tampered with. The cheapest-carbon window wins. If acarbonBudgetGwas supplied, windows that exceed the budget are dropped first; if no window survives, the task fails withCarbonBudgetExceededError. - Wait. A timer is set for the chosen window. Long horizons are chained to avoid Node's 32-bit-millisecond
setTimeoutoverflow. - Dispatch. When the timer fires, the scheduler calls the task body. If the body is a provider-adapter descriptor (Anthropic / OpenAI), and the deadline is more than 24 hours out, the adapter routes through the provider's Batch API for the 50% discount. Otherwise it uses the standard sync endpoint.
- Receipt. The dispatch writes a carbon receipt: timestamp, region, both estimated grams (projected at schedule time from the forecast entry the scheduler scored) and actual grams (billed at dispatch time, re-estimated with the provider's reported input/output token usage and the actual model that ran), plus signed
deltaPctdrift. Provider model, duration, tokens. The audit trail is honest about what was scored and what actually shipped. - Return. The result resolves the caller's promise (library) or becomes visible via
check_queue_status(task_id)(MCP). The receipt persists.
What's deliberately not in v0.11
- Cross-provider routing. Tidegap picks the cheapest carbon window inside one provider. It doesn't yet weigh "Anthropic vs OpenAI vs Gemini" on a per-task basis. On the roadmap, once the per-provider adapters have stabilized.
- Local hardware routing. "If your laptop is plugged in and the grid is clean, use Ollama" is a real product idea, deferred to a later release.
- Multi-region failover. If your default region's grid is unusually dirty for a sustained window, Tidegap could route through a different provider region. On the roadmap.
- Global aggregate leaderboard. Per-user impact is local-only today (privacy-by-default). A signed opt-in telemetry endpoint and aggregate counters are designed — see
docs/spec/proposal/v09-leaderboard.md.