Temporal causal graph for AI agents. PostgreSQL-backed directed graph of historical moments — canonical spatiotemporal URLs, typed causal edges, autonomous expansion, and browse/search/discovery APIs.
Note
Why this exists — AI agents that reason about causality across time currently rely on web search (noisy, unstructured), knowledge graphs (no temporal dimension), or hallucination. The Clockchain is a structured alternative: every node carries dialog, entity states, provenance, and confidence, addressed by a canonical spatiotemporal URL, in a format (TDF) designed for machine consumption.
The graph accumulates two layers of rendered reality:
- Rendered Past — historical events rendered by Flash with full causal structure, entity states, dialog, and source grounding
- Rendered Future — simulation outputs from Pro, scored for convergence, stored as TDF records
Each new event with causal edges tightens the Bayesian prior — fewer plausible things could have happened in the gaps — approaching asymptotic coverage of any historical period.
The name is conceptual. This is PostgreSQL, not a blockchain.
flowchart TD
F["Flash scenes"] --> G["Clockchain Graph\n(PostgreSQL)"]
P["Pro simulations"] --> G
E["Expander\n(LLM-driven)"] --> G
G --> BP["Stronger Bayesian Prior"]
BP -. "fewer plausible gaps\nbetter future renderings" .-> G
Part of the Timepoint AI suite — the Clockchain is the temporal causal graph that all other services read from and write to.
Deployed at: clockchain.timepointai.com (via API gateway at api.timepointai.com/api/v1/clockchain/*)
The Clockchain is a directed graph stored in PostgreSQL (two tables: nodes and edges). Each node is a historical moment identified by a canonical temporal URL. Edges encode typed relationships between moments.
Every node has a unique spatiotemporal address — 8 segments encoding when and where:
/-44/march/15/1030/italy/lazio/rome/assassination-of-julius-caesar
│ │ │ │ │ │ │ └── slug (auto-generated)
│ │ │ │ │ │ └─────── city
│ │ │ │ │ └──────────── region
│ │ │ │ └───────────────── country (modern borders)
│ │ │ └───────────────────── time (24hr, no colon)
│ │ └───────────────────────── day
│ └────────────────────────────── month (lowercase name)
└─────────────────────────────────── year (negative = BCE)
Negative years for BCE, all segments kebab-case. This path is both the node's primary key and its API address.
| Layer | Content | Storage |
|---|---|---|
| 0 | URL path + event name | Clockchain graph (Postgres nodes table) |
| 1 | Metadata: figures, tags, one-liner description | Clockchain graph (Postgres nodes table) |
| 2 | Full Flash scene reference | flash_timepoint_id field in graph node |
| Type | Meaning | Auto-linked? |
|---|---|---|
causes |
Direct causal relationship | No — expander or manual only |
caused_by |
Inverse causal relationship | No — expander or manual only |
influences |
Indirect influence | No — expander or manual only |
precedes |
Temporal ordering | No — expander or manual only |
follows |
Inverse temporal ordering | No — expander or manual only |
same_era |
Same year (+/- 1) | Yes, weight 0.5 |
same_location |
Matching country + region + city | Yes, weight 0.5 |
same_conflict |
Part of the same military/political conflict | No — expander or manual only |
same_figure |
Shared historical figure | No — expander or manual only |
thematic |
Overlapping tags (array overlap) | Yes, weight 0.3 |
contemporaneous |
Legacy alias for same_era |
Supported for backwards compatibility |
When a node is added via add_node(), the _auto_link() method creates bidirectional edges for same_era, same_location, and thematic using efficient INSERT...SELECT queries.
Two tables with indexes:
nodes (id TEXT PK, type, name, year, month, month_num, day, time,
country, region, city, slug, layer, visibility, created_by,
tags TEXT[], one_liner, figures TEXT[], flash_timepoint_id,
flash_slug, flash_share_url, era, image_url,
confidence FLOAT, source_run_id, tdf_hash, source_type,
schema_version, text_model, image_model,
model_provider, model_permissiveness, generation_id,
graph_state_hash, created_at, published_at)
edges (source TEXT FK, target TEXT FK, type TEXT CHECK(...),
weight FLOAT, theme TEXT, description TEXT, created_by TEXT,
schema_version TEXT, source_run_id TEXT,
PK(source, target, type))Indexes on: visibility, (month, day), year, (country, region, city), GIN on tags/figures, GIN trigram on name/one_liner (when pg_trgm is available).
Every node tracks which models generated its content:
| Field | Meaning |
|---|---|
text_model |
LLM used for text (e.g. deepseek/deepseek-chat-v3-0324) |
image_model |
Model used for images (e.g. stabilityai/stable-diffusion-3) |
model_provider |
Provider name (e.g. deepseek, stabilityai) |
model_permissiveness |
Policy tier (permissive, standard, unknown) |
generation_id |
Unique generation run ID |
schema_version |
Schema version (0.1, 0.2) |
Clockchain enforces permissive-only models — blocked providers (google, anthropic, openai) are rejected at the compliance gate.
| Type | Meaning |
|---|---|
historical |
Verified historical event (seed data or curated) |
expander |
Generated by autonomous graph expansion (LLM-driven) |
simulation |
Output from Pro temporal simulation |
predicted |
Rendered Future awaiting validation |
Clockchain nodes are expressible as TDF records via timepoint-tdf. The TDF bridge (app/core/tdf_bridge.py) promotes model provenance fields to TDFProvenance (TDF v1.2.0+), with backwards-compatible fallback for older TDF versions.
Proof of Causal Convergence (PoCC) is a future protocol concept: multiple independent renderings that converge on the same causal structure provide validation without ground truth. The Clockchain is the natural accumulation point for convergent paths.
Five background workers handle content generation and graph growth:
| Worker | File | Purpose | Feature Flag |
|---|---|---|---|
| Renderer | app/workers/renderer.py |
HTTP client for Flash scene generation; supports model_policy parameter for permissive routing |
Always available |
| Expander | app/workers/expander.py |
LLM-driven autonomous graph growth; picks low-degree frontier nodes and generates related events via OpenRouter | EXPANSION_ENABLED=true + OPENROUTER_API_KEY |
| Judge | app/workers/judge.py |
LLM content moderation gate; classifies queries as approve/sensitive/reject via OpenRouter | Used during generation flow |
| Daily | app/workers/daily.py |
"Today in History" cron; finds date-matching events without Flash scenes and queues generation | DAILY_CRON_ENABLED=true |
| Iterator | app/workers/iterator.py |
Universal enhancement passes over all nodes; backfills era labels and images with provenance firewall (immutable/backfillable/mutable fields) | Always runs |
pick frontier node (degree < 3)
↓
LLM generates 3–5 related events
↓
add_node() → _auto_link()
· causal edges to source node
· temporal / spatial / thematic auto-edges
↓
sleep 300s → repeat
The Expander runs on a configurable interval (default 300s). The Daily worker adds a parallel growth path: every 24 hours, events matching today's date get queued for Flash scene rendering (up to 5 per day). The Iterator runs enhancement passes (era backfill, image backfill) every 600s.
Interactive API docs are available at /docs (Swagger UI) and /redoc (ReDoc). The OpenAPI spec is served at /openapi.json. Authenticated endpoints show a lock icon — click "Authorize" in Swagger UI to enter your X-Service-Key.
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Health check |
GET |
/api/v1/moments |
Paginated moment list with filters (year range, entity, text search, confidence) |
GET |
/api/v1/moments/{path} |
Full moment data by canonical URL (public moments; auth unlocks private) |
GET |
/api/v1/stats |
Enhanced graph statistics (node/edge counts, model breakdown, image coverage) |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/browse |
List root segments (years) |
GET |
/api/v1/browse/{path} |
Hierarchical path listing (public moments only) |
GET |
/api/v1/today |
Events matching today's month/day |
GET |
/api/v1/random |
Random public moment (Layer 1+) |
GET |
/api/v1/search?q={query} |
Full-text search (ILIKE + array unnest for tags/figures) |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/graph/neighbors/{path} |
Connected nodes with edge metadata |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/generate |
Queue scene generation via Flash |
GET |
/api/v1/jobs/{job_id} |
Poll job status |
POST |
/api/v1/moments/{path}/publish |
Set visibility to public |
POST |
/api/v1/bulk-generate |
Bulk generation (requires ADMIN_KEY) |
POST |
/api/v1/index |
Add or update a moment in the graph |
POST |
/api/v1/expand-once |
Trigger one expansion cycle (requires OPENROUTER_API_KEY) |
POST |
/api/v1/ingest/subgraph |
Bulk-ingest a causal subgraph (nodes + edges) |
POST |
/api/v1/ingest/tdf |
Ingest TDF records from sibling services |
timepoint-clockchain/
├── app/
│ ├── main.py # FastAPI app, lifespan, OpenAPI security, health
│ ├── api/
│ │ ├── __init__.py # API router aggregation
│ │ ├── public.py # /moments (public), /stats (public)
│ │ ├── moments.py # /moments (auth), /browse, /today, /random, /search
│ │ ├── generate.py # /generate, /jobs, /publish, /bulk-generate, /index
│ │ ├── graph.py # /graph/neighbors
│ │ ├── ingest.py # /ingest/subgraph, /ingest/tdf
│ │ └── agents.py # /agents/register, /agents, /agents/{id} (multi-writer)
│ ├── core/
│ │ ├── config.py # Settings (pydantic-settings)
│ │ ├── auth.py # Service key validation (hmac) + OpenAPI scheme
│ │ ├── multi_writer.py # Multi-writer token auth, agent identity
│ │ ├── db.py # asyncpg pool, schema DDL, migrations, seeding
│ │ ├── graph.py # PostgreSQL-backed GraphManager (async)
│ │ ├── url.py # Canonical temporal URL system
│ │ ├── jobs.py # In-memory job queue + compliance gate
│ │ ├── tdf_bridge.py # TDF v1.2.0 ↔ clockchain node conversion
│ │ ├── model_selector.py # Permissive model resolution via OpenRouter
│ │ └── rate_limit.py # slowapi rate limiting
│ ├── workers/
│ │ ├── renderer.py # Flash HTTP client (model_policy support)
│ │ ├── expander.py # Autonomous LLM-driven graph growth loop
│ │ ├── iterator.py # Enhancement passes (era backfill, image backfill)
│ │ ├── judge.py # LLM content moderation (OpenRouter)
│ │ └── daily.py # "Today in History" daily worker
│ └── models/
│ └── schemas.py # Pydantic response/request models
├── data/
│ ├── seeds.json # 5 seed historical events (JSON)
│ └── seeds.jsonl # 5 seed historical events (JSONL, preferred)
├── migrations/ # SQL migration scripts
├── scripts/
│ └── migrate_graph_json.py # One-time migration from graph.json to Postgres
├── tests/
├── Dockerfile
├── railway.json
└── pyproject.toml
# Install dependencies
pip install -e ".[dev]"
# Or with uv
uv sync
# Copy env template and fill in your keys
cp .env.example .envClockchain requires a PostgreSQL database. The schema is created automatically on startup.
# Local dev (macOS)
brew services start postgresql@16
createdb clockchain
# Or via Docker
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=test -e POSTGRES_DB=clockchain postgres:16Set DATABASE_URL in your .env:
DATABASE_URL=postgresql://localhost:5432/clockchain
uvicorn app.main:app --port 8080On startup, the service:
- Creates an asyncpg connection pool
- Runs schema DDL (CREATE TABLE IF NOT EXISTS)
- Runs SQL migrations (schema evolution)
- Seeds the database from
seeds.jsonlif the nodes table is empty - Starts the GraphManager, workers, and API server
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL |
Yes | PostgreSQL connection URL | |
SERVICE_API_KEY |
Yes | Shared secret for inbound service auth | |
FLASH_URL |
No | https://flash.timepointai.com |
Flash service URL |
FLASH_SERVICE_KEY |
Yes | Auth key for Flash API calls | |
DATA_DIR |
No | ./data |
Directory for seed data |
ENVIRONMENT |
No | development |
Environment name |
DEBUG |
No | false |
Enable debug logging |
PORT |
No | 8080 |
Server port |
OPENROUTER_API_KEY |
No | OpenRouter API key (for expander + judge + model selector) | |
OPENROUTER_MODEL |
No | Override model for AI workers (must be permissive) | |
EXPANSION_ENABLED |
No | false |
Enable autonomous graph expansion |
EXPANSION_INTERVAL |
No | 300 |
Seconds between expansion cycles |
EXPANSION_CONCURRENCY |
No | 1 |
Parallel expansion tasks |
EXPANSION_TARGET |
No | 0 |
Target node count (0 = unlimited) |
EXPANSION_DAILY_BUDGET |
No | 5.0 |
Daily spending limit (USD) |
DAILY_CRON_ENABLED |
No | false |
Enable "Today in History" worker |
ADMIN_KEY |
No | Key for bulk generation endpoint | |
ADMIN_TOKEN |
No | Admin Bearer token for multi-writer agent management | |
WRITER_TOKENS |
No | Comma-separated token:agent_name pairs for bootstrap |
|
RATE_LIMIT_PUBLIC |
No | 60/minute |
Rate limit for unauthenticated endpoints |
RATE_LIMIT_AUTH_READ |
No | 300/minute |
Rate limit for authenticated reads |
RATE_LIMIT_AUTH_WRITE |
No | 30/minute |
Rate limit for authenticated writes |
CORS_ORIGINS |
No | Extra CORS origins (comma-separated) |
Clockchain supports multi-writer mode where multiple agents can propose and challenge moments. When enabled, write operations require a Bearer token in the Authorization header.
Set ADMIN_TOKEN and/or WRITER_TOKENS to enable multi-writer auth:
# Admin token — can register/revoke agents
ADMIN_TOKEN=your-admin-secret
# Bootstrap writer tokens — comma-separated token:name pairs
WRITER_TOKENS=tok1:agent-alpha,tok2:agent-betaWhen neither is set, auth is disabled and the system operates in legacy single-writer mode (backward compatible).
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/agents/register |
Register a new agent (admin-only), returns token |
GET |
/api/v1/agents |
List registered agents (admin-only) |
DELETE |
/api/v1/agents/{agent_id} |
Revoke an agent's access (admin-only) |
When multi-writer auth is enabled, moments track which agent proposed them:
proposed_by— set automatically from the authenticated agent's identitychallenged_by— list of agent identities that challenged this moment
# Register a new agent (admin)
curl -X POST https://clockchain.example.com/api/v1/agents/register \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"agent_name": "my-agent", "permissions": "write"}'
# Use the returned token for write operations
curl -X POST https://clockchain.example.com/api/v1/index \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H "X-Service-Key: $SERVICE_KEY" \
-H "Content-Type: application/json" \
-d '{"path": "/1945/august/6/1200/japan/hiroshima/hiroshima/test", "metadata": {}}'Clockchain is publicly accessible at clockchain.timepointai.com. It is also reachable via the API gateway at api.timepointai.com/api/v1/clockchain/* (the gateway strips the clockchain/ prefix).
Service-to-service calls (from Flash, gateway, etc.) use the X-Service-Key header. The gateway injects X-Service-Key, X-User-Id, and X-User-Email headers on proxied requests.
Tests run against a real PostgreSQL database (no mocks):
createdb clockchain_test
DATABASE_URL=postgresql://localhost:5432/clockchain_test pytest tests/ -vDeployed on Railway via a private deploy repo. See that repo for Railway configuration, entrypoint behavior, and production environment details.
5 initial events loaded from data/seeds.jsonl when the database is empty:
- Assassination of Julius Caesar (-44 BCE)
- Trinity Test (1945)
- Apollo 12 Lightning Launch (1969)
- Apollo 11 Moon Landing (1969)
- AlphaGo Move 37 (2016)
Render the past. Simulate the future. Score the predictions. Accumulate the graph.
| Service | Type | Repo | Role |
|---|---|---|---|
| Flash | Open Source | timepoint-flash | Reality Writer — renders grounded historical moments |
| Clockchain | Open Source | timepoint-clockchain | Temporal Causal Graph — Rendered Past + Rendered Future, growing 24/7 |
| Pro | Open Source | timepoint-pro | SNAG Simulation Engine — temporal simulation, TDF output, training data |
| Proteus | Open Source | proteus | Settlement Layer — prediction markets for Rendered Futures |
| TDF | Open Source | timepoint-tdf | Data Format — JSON-LD interchange across all services |
| SNAG Bench | Open Source | timepoint-snag-bench | Quality Certifier — measures Causal Resolution across renderings |
| Billing | Private | timepoint-billing | Payment Processing — Apple IAP + Stripe |
| MCP | Private | timepoint-mcp | MCP Server — AI agent access to Flash and Clockchain |
| Web App | Private | timepoint-web-app | Browser client at app.timepointai.com |
| Landing | Private | timepoint-landing | Marketing site at timepointai.com |
| iPhone App | Private | timepoint-iphone-app | iOS client — Synthetic Time Travel on mobile |
| API Gateway | Private | timepoint-api-gateway | Reverse proxy at api.timepointai.com |
| Skip Meetings | Private | skipmeetingsai | Meeting intelligence SaaS powered by Flash |
Sean McDonald (@seanmcdonaldxyz · realityinspector)
License: Apache 2.0