For Agents
Living index of themes for this project. Each H2 is a topic; bullets are wikilinks to related notes. Updated by
obsidian-documenterwhen documenting work. Read byhistorianat bootstrap. Topics kept alphabetical.
Agent Guides
- agent-guide-provision-new-vm — how-to for provisioning more VMs using the Spec 1 stack (architecture reality, prerequisites, pitfalls)
- agent-guide-configure-app-deploy — how-to for deploying an app onto
ops-vmtoday (manual path) and the Spec 2 features that will replace it
Artifact Registry & CI
- gcp-app-deploy-design — Spec 2a Terraform
modules/ar_wif/(AR Docker repoappsineurope-west3+ WIF pool/provider +ci-pusherSA + per-repo IAM bindings); reusablebuild-push.ymlGitHub Actions workflow called viaworkflow_callfrom each app repo - gcp-terraform-ansible-gotchas —
deploy-apps.ymlconcurrency group withcancel-in-progress: falsecancels the intermediate pending run when 3+ deploys are dispatched at once; dispatch sequentially (gotcha #24) - gcp-terraform-ansible-gotchas —
deploy-apps.yml“Materialize per-app env file” step reads the GitHub Actions repo secret, not the local gitignoredsecrets/<app>.env; local edits never reach GH-Actions-driven deploys (gotcha #25)
Ansible Configuration
- gcp-app-deploy-design — Spec 2a adds the
appsrole (compose + systemd templates) and extends thedockerrole with the AR credential helper - gcp-vm-provisioning-design — the four-role Ansible layer (base, docker, github_keys, monitoring),
ansible.cfgloading, explicit fact-gathering afterwait_for_connection - gcp-terraform-ansible-gotchas —
ansible.cfgauto-load gotcha,gather_factsordering gotcha - spec-1-deployment-complete — Ansible roles all green against live
ops-vm - spec-1-operations-runbook — re-applying Ansible via
make configureon the live VM - babylon-deploy-notes — new
babylonrole (rustup install + cargo build withcreates:short-circuit + systemd + litestream + idempotenttailscale serve), brings the role count from 5 to 6
Backup & DR
- babylon-deploy-notes — Litestream continuous SQLite WAL replication to GCS bucket
polymarket-infra-babylon-backup(versioned, 30d + ≥3 newer-versions lifecycle); GCE-native ADC instead of SA JSON key (org policyiam.disableServiceAccountKeyCreationblocks key creation)
App Deploy
- gcp-app-deploy-design — Spec 2a approved design: AR + WIF + Ansible
appsrole (serviceandjobruntime shapes) + central manifest + EU migration + first app (polymarket-fetch) - spec-2-roadmap — Artifact Registry +
docker compose+ Cloudflare ZTNA + batch jobs + app logs/traces; the application-deploy layer on top ofops-vm - agent-guide-configure-app-deploy — manual deploy path today (
/opt/apps/<name>, named compose projects, OTel-via-localhost) and what Spec 2 replaces it with - gcp-terraform-ansible-gotchas — GH-Actions deploys read env values from the repo secret, not the local file; “successful” deploys can silently leave the
/opt/apps/<app>/.envmtime untouched for days because Ansible’scopyis content-hashed (gotcha #25) - crypto-emit-max-fires-cap-clear-2026-06-25 — Pattern A → B cutover residue: post-cutover containers can inherit DB-state that hydrates into in-process safety counters at boot, leaving the new service in a “paused” state until manually cleared (TRUNCATE + container restart). Worth checking on ANY Pattern A → B cutover involving DB-hydrated guardrails
- cohort-firstmover-roll-d851d44-2026-06-25 —
apps-pmv2-cohort-algo-firstmoverpin8977b5b→d851d44: on-chain feed migrated Alchemy WSS log-subscription →eth_getLogsHTTP polling via the 150-proxy rotating pool (kills cohort WSS CU bleed); consensus-alert threshold 2 → 4; PM self-watch (pmv2-onchain-watch) is on a SEPARATE WSS path and STILL uses Alchemy WSS — don’t conflate the two lanes - weather-as-dry-publish-arm-2026-06-25 —
apps-pmv2-weather-as-algopin9a07960→e5521c4+ env armWEATHER_AS_PUBLISH=1: first time the weather-AS lane has a path to publish entry envelopes to NATSpmv2.order.weather_as.entry; env-gated default OFF, once-per-market/day dedup via self-measure ledger; money safety intact (dry_run end-to-end, PM gates at build+sign); kill switch = unset env + re-push secret + redeploy
Cost & Sizing
- spec-1-deployment-complete — ~$18/mo at e2-small + 20 GB pd-balanced + ephemeral external IPv4
- spec-1-operations-runbook — billing console URL, ~24-48h lag note, resize procedure
- spec-2-roadmap — open decision on Cloud NAT to drop the $3.65/mo external IPv4
Docker & Compose
- babylon-deploy-notes — 3-service compose (
babylon+litestream+babylon-tailscalesidecar), bind-mounted binary,ubuntu:24.04runtime image to match host glibc - gcp-terraform-ansible-gotchas — GLIBC mismatch between Ubuntu 24.04 host and
debian:bookworm-slimruntime (gotcha #29); composehostname:overrides bridge-network DNS for the matching name inside the container (gotcha #31) - signoz-dashboard-housekeeping-2026-06-18 —
prometheus-nats-exportersidecar added to the nats compose project (2026-06-18); connects to NATS over the compose-internal docker network athttp://nats:8222, exposes Prometheus metrics on127.0.0.1:7777for the host-native OTel collector to scrape
EU Migration
- gcp-app-deploy-design — Spec 2a migrates the existing
ops-vmfromus-central1-atoeurope-west3-a(destroy + reprovision; samevm_nameso MagicDNS resolution stays unchanged)
Gotchas & Learnings
- gcp-terraform-ansible-gotchas — thirty-eight reusable GCP / Terraform / Ansible / OTel / RTK / GitHub Actions / Rust / Tailscale / Docker / SigNoz / NATS / process-lifecycle traps from Spec 1 validation, live deployment, Spec 2a/2b execution, the CD workflow rollout, the GH-secret vs local-env divergence, the 2026-06-09 babylon Rust deploy, the babylon compose refactor (sidecar pattern), the 2026-06-18 SigNoz/NATS housekeeping (#34–#37), and the 2026-06-25 crypto emit cap clear (#38 in-process DB-hydrated counters only re-read on boot)
- crypto-emit-max-fires-cap-clear-2026-06-25 — Pattern A → B cutover residue case study: 50 pre-cutover rows in
crypto_shortterm.emitted_signals+ defaultEMIT_MAX_FIRES=50left the new container in a paused boot state for ~22h; TRUNCATE + container restart fixed it - spec-1-operations-runbook — the three gotchas most likely to bite on a re-run, with cross-links to the full reference
- spec-1-retrospective — meta-reflection on which gotchas the two-stage review caught vs which surfaced only at deployment time
- babylon-deploy-notes — where gotchas #26 (cold Rust compile time ~35 min on e2-small → bump workflow timeout to 90 min) and #27 (
libssl-devnot needed with rustls + aws-lc-rs; usecmakeinstead) surfaced - signoz-dashboard-housekeeping-2026-06-18 — where gotchas #34 (SigNoz
create_dashboardHTML-escapes&), #35 (host metrics dotted-vs-underscore form), #36 (OTelhost_metrics*.utilizationseries OFF by default), #37 (NATS:8222is JSON, not Prometheus — needsprometheus-nats-exportertranslation sidecar) surfaced
How-to / Agent Guides
- agent-guide-provision-new-vm — agent-facing step-by-step for spinning up another VM
- agent-guide-configure-app-deploy — agent-facing step-by-step for deploying an application onto
ops-vm
Local OTel Store (post-SigNoz)
- pmv2-zurich-local-otel-store — SigNoz Cloud EU2 workspace expired;
otelcol-contribonpmv2-zurichcut over to threefileexporters writing JSONL to/var/log/otel/(logs 7d/200 backups, traces 3d/100, metrics 1d/30, 100 MB rotate). DuckDB v1.1.3 +otelqwrapper (errors|recent|svc|grep|stats|services|sql|raw|disk) for queries. Files are0644via systemdUmask=0022drop-in;opsinotelcol-contribgroup — no sudo needed. Includes 7-step break-glass runbook: missing tools → re-apply monitoring role (NOTmake deploy); empty files → systemctl + journal; disk fill → rm rotated backups + retention knob inroles/monitoring/defaults/main.yml; Permission denied →usermod -aG otelcol-contrib ops;Binder Error→ patchFLAT_LOGSCTE inroles/monitoring/tasks/main.yml(never/usr/local/bin/otelqon VM); restore OTLP vendor → dual exporter inconfig.yaml.j2+secrets/signoz_ingestion_key; config source of truth =ansible/roles/monitoring/in terraform repo - pmv2-zurich-local-otel-store — pre-existing coverage gap re-surfaced:
pmv2-position-manager,pmv2-shortterm-crypto-emit/observe,pmv2-telegram-connector(Rust) don’t export logs (theirtracing-opentelemetrycrate has no log exporter by design; fix = addopentelemetry-appender-tracingbridge in each service repo). Traces + metrics DO flow. For those services,docker logs <container>is still the log path - pmv2-zurich-local-otel-store — Zurich↔Dublin latency context:
pmv2-zurichis GCPeurope-west6-a; Supabase mgmt DBmkofmdtdldxgmmolxxhciseu-west-1(~1400 km, 30–50 ms RTT per SELECT); PMgates_msmedians ~730 ms across 4 sequential config SELECTs pre-book-fetch may reflect pool non-amortization
NATS & JetStream
- signoz-dashboard-housekeeping-2026-06-18 —
prometheus-nats-exporter:0.17.3sidecar in the nats compose project translates NATS’s JSON:8222endpoints into a Prometheus scrape target on127.0.0.1:7777; OTel collector picks it up via itsprometheusreceiver; new SigNozpolymarket-infra — NATS + JetStreamdashboard (UUID019eda9d-139f-7526-b22d-c4d8a4a3cdf2) keys off these metrics — pmv2 consumer backlog (pending + ack_pending) is the headline panel for “is the consumer keeping up” - observability-flow — NATS Prometheus metrics path documented alongside host/Tailscale/traces/logs as the fifth signal source feeding the OTel collector
- gcp-terraform-ansible-gotchas — NATS
:8222is JSON, NOT Prometheus — needsprometheus-nats-exportertranslation;-jsz=allflag is required for JetStream stream/consumer metrics; do NOT expose the exporter port publicly (-connzleaks client IPs,-subzleaks subject names) (gotcha #37) - crypto-emit-max-fires-cap-clear-2026-06-25 — JSM stream subject-capture proof recipe:
docker exec apps-nats-nats-1 wget -qO- 'http://127.0.0.1:8222/jsz?streams=true&config=true&consumers=false'returns the full stream config (subjects: [...]) + state (last_seq) from inside the nats container without admin NKey CLI access; useful when ruling NATS in/out as the cause of “no publishes are landing”; also captures thecohort_publisherNKey publish-permit set (allowpmv2.order.crypto_shortterm_latency_test.>+$JS.API.>+$JS.ACK.>, sub_INBOX.>) - weather-as-dry-publish-arm-2026-06-25 — first publisher on subject
pmv2.order.weather_as.entry(env-gated byWEATHER_AS_PUBLISH, default OFF); weather_as NKey already had PUB permit onpmv2.order.weather_as.>from prior session (pin5db34c0); subject is captured byPMV2_ORDERSwildcardpmv2.order.>— no NATS-side change needed for the arm
Networking & Tailscale
- gcp-vm-provisioning-design — dedicated custom VPC, no public SSH, Tailscale MagicDNS access,
tag:cloud, Tailscale SSH ACL - gcp-terraform-ansible-gotchas —
defaultVPC open SSH rule, OS Login override, Tailscale SSH ACLautogroup:selfvs tagged devices; sidecar--advertise-tagsrejection (tagOwners not key, gotcha #32); composehostname:shadowing sibling-service DNS in sidecar containers (gotcha #31); babylon’s funnel-CLI safety check needsBABYLON_ALLOW_FUNNEL=1when sidecar owns the tailnet edge (gotcha #30) - spec-1-deployment-complete — late refinement from key-based SSH to Tailscale SSH; ACL targets
tag:cloud - spec-1-operations-runbook —
ssh ops@ops-vmandtailscale ssh ops@ops-vmaccess paths - babylon-deploy-notes —
tailscale serve --bg http://127.0.0.1:8787to publish a loopback port as HTTPS on the tailnet (NOTfunnel; that exposes to the public internet); idempotent Ansible task pattern viatailscale serve status --json+ handler check; compose refactor (2026-06-09) gives babylon its own tailnet identitybabylon.taild4189d.ts.netvia a Tailscale sidecar (separate frompolymarket-infra)
Operations
- spec-1-operations-runbook — day-2 access, health checks, common operations (re-apply / resize / teardown), logs, where things live, cost monitoring, secret rotation
- agent-guide-configure-app-deploy — day-1 app deploy hygiene that survives into Spec 2 (per-app folders, named compose projects, localhost-bound ports)
- pmv2-zurich-local-otel-store — day-2 query surface for the local OTel store on
pmv2-zurich:otelq errors|recent|svc|grep|stats|services|sql|raw|disk, no sudo required, all output is JSONL under/var/log/otel/ - babylon-deploy-notes — babylon operational layout on
polymarket-infra(binary / SQLite / systemd / litestream /tailscale servepaths), sizing footprint (~19 MB + ~14 MB),babylon-serverCLI surface (mint/rotate/revoke tokens), and the “tokens shown plaintext once” rule - babylon-migration — fleet cutover from
AGENT_HANDOFF.mdto babylon (4 phases over 24-48h; Phase 0 done, Phase 1 pilot in progress withdeploy+operator); channel scheme; carry-over-as-tasks history-import decision - babylon-deploy-notes — 2026-06-25 redeploy:
babylon-compose.servicepin2f6a6eb→d0f5151viadeploy-babylon.ymlrun 28189267339; brings issue tracker MCP tools,#babylon-newsauto-subscribe, and additive migration0002_issues.sqllive; no env-var changes; verified bylist_issuesreturning{"issues":[]}
Process
- spec-1-retrospective — RPI-style brainstorm → spec → plan → execute → validate workflow with subagent-driven-development; what worked, what needed mid-stream adjustment, surprises during live run
Provisioning & Design
- gcp-app-deploy-design — Spec 2a design: extends the Terraform root with
modules/ar_wif/, adds theappsAnsible role, migrates the deployment toeurope-west3 - levandor-infra — project overview, two-spec roadmap, Spec 1 deployed
- gcp-vm-provisioning-design — approved Spec 1: Terraform-provisions / Ansible-configures VM lifecycle
- spec-1-deployment-complete — live deployment state:
ops-vme2-small in us-central1-a, on the tailnet, Docker + fail2ban + OTel - agent-guide-provision-new-vm — agent walkthrough for
make preflight → plan → provision → verifyagainst this Terraform root, plus the multi-VM refactor constraint - babylon-deploy-notes — operational layout of the babylon Rust MCP service on
polymarket-infra(separate Ansible role + dedicated CD workflow alongside the containerized-apps pipeline)
Roadmap
- gcp-app-deploy-design — Spec 2a approved (core app deploy + EU migration + first app)
- spec-2-roadmap — application-deploy layer (Artifact Registry,
docker compose, Cloudflare ZTNA, batch jobs, app telemetry); open design decisions; next-task list
Rust on the VM
- babylon-deploy-notes — building axum + OpenTelemetry + sqlx + aws-lc-rs in-place on
polymarket-infra(e2-small) via thebabylonAnsible role; cargo task gated oncreates: <binary path>so re-runs short-circuit - gcp-terraform-ansible-gotchas — gotcha #26 cold-build wall-clock (~35 min on e2-small) and the 90-min workflow timeout it forces; gotcha #27
libssl-devvscmakeapt prereq divergence between openssl-sys and aws-lc-sys crates (usecargo tree -ito confirm before writing the apt list)
Secrets & Auth
- gcp-app-deploy-design — Spec 2a uses Workload Identity Federation (OIDC) for CI to AR auth (no long-lived service-account keys); per-app
secrets/<app>.envfiles, gitignored, mode 0600, Ansible-copied to/opt/apps/<app>/.env - gcp-vm-provisioning-design — ADC auth, Tailscale auth key with
tag:cloud, SigNoz ingestion key, passwordless operation - spec-1-deployment-complete — Tailscale SSH replaces SSH keypair + macOS Keychain step entirely
- spec-1-operations-runbook — secret rotation procedures for the Tailscale auth key and SigNoz ingestion key
- gcp-terraform-ansible-gotchas — for apps deployed via
deploy-apps.yml, the GitHub Actions repo secret (e.g.POLYMARKET_FETCH_ENV) is the source of truth, not the local gitignoredsecrets/<app>.env; rotate env vars by piping a file intogh secret set -R <repo> < filethen re-dispatching the workflow (gotcha #25) - babylon-deploy-notes — under org policy
iam.disableServiceAccountKeyCreation, Litestream → GCS auths via the VM’s own service-account ADC through the GCE metadata server (no JSON key, nocredentials_path:in/etc/litestream.yml); bucket-scopedroles/storage.objectAdmingrant on the VM SA is sufficient. Same pattern applies to any GCE-resident GCS client. Babylon agent tokens are minted bybabylon-server mint-token <handle>and shown plaintext once — no read-back, lost token requiresrotate-token; values distributed via per-repo GH Actions secrets, not in this vault
SigNoz Dashboards
- signoz-dashboard-housekeeping-2026-06-18 — 2026-06-18 housekeeping pass: new
polymarket-infra — NATS + JetStreamdashboard (UUID019eda9d-139f-7526-b22d-c4d8a4a3cdf2, 9 panels including pmv2 backlog pending+ack_pending and redeliveries); updatedpolymarket-infra — App services (APM)(UUID019e6a21-9eee-7e14-9603-1e00ebd59194) with log-severity split and ERROR log-rate panels; updatedpolymarket-infra — Host & Tailscale(UUID019e6a1c-5be5-7383-83ff-23f8562038f4) with disk I/O bytes+IOPS, swap paging ops, network errors+drops, tailscaled uptime, andhost.name=polymarket-infrascoping on the Tailscale-internal panels - observability-flow — three production dashboards (host & Tailscale, App services APM, NATS + JetStream) all live in SigNoz Cloud EU2; reach via
https://eu2.signoz.cloud/dashboard/<uuid> - gcp-terraform-ansible-gotchas — SigNoz
create_dashboardHTML-escapes&in titles, use+orupdate_dashboardafterward (gotcha #34); host metrics stored under dotted canonical names but legacy widgets reference underscore form, both query-equivalent (gotcha #35)
Telemetry & Monitoring
- gcp-vm-provisioning-design — OpenTelemetry Collector host metrics to SigNoz Cloud (eu2)
- gcp-terraform-ansible-gotchas — OTel
:8888/metricsas export-success signal, v0.152 receiver/exporter renames; OTelhost_metrics*.utilizationseries OFF by default (gotcha #36); NATS:8222is JSON not Prometheus, needsprometheus-nats-exportertranslation sidecar (gotcha #37) - spec-1-deployment-complete — ~25k host-metric points sent to
ingest.eu2.signoz.cloud, zero failures - spec-1-operations-runbook —
curl :8888/metricshealth-check and OTel log location for live debugging - spec-2-roadmap — extending
otelcol-contribwithfilelogfor logs and OTLP receivers for traces - agent-guide-configure-app-deploy — manual recipe for extending the collector config with
otlp+filelogreceivers for app logs/traces before Spec 2 lands - signoz-dashboard-housekeeping-2026-06-18 — NATS Prometheus metrics path stood up via
prometheus-nats-exporter:0.17.3sidecar (bound to127.0.0.1:7777, flags-varz -connz -subz -routez -gatewayz -leafz -jsz=all); four host_metrics*.utilizationscrapers explicitly opted in (CPU/FS/memory/paging — host dashboard CPU+FS panels had been silently empty since Spec 1) - pmv2-zurich-local-otel-store — post-SigNoz-expiry cutover on
pmv2-zurich: threefileexporters write JSONL to/var/log/otel/(100 MB rotate, retention 7d/3d/1d, capped backups 200/100/30), queried via DuckDB +otelqwrapper baking the OTLP-JSONFLAT_LOGSunnest CTE; files are0644via systemdUmask=0022drop-in so no sudo needed. Sixth OTel signal source description alongside host / Tailscale / NATS / traces / logs
Tooling & Workflow
- gcp-terraform-ansible-gotchas —
rtk proxybypasses RTK output filtering when raw output is load-bearing (Terraform/Ansible/curl);gh workflow runconcurrency-group dispatch race (gotcha #24); RTK output leaks intogh secret setstdin when piping — alwaysrtk proxy gh secret set …(gotcha #28); ansible-e key=$VARshlex-parse breaks on shell-special bytes, use-e @file.jsonbuilt viajq(gotcha #33)
Troubleshooting
- spec-1-operations-runbook — common gotchas section keyed to the three most likely re-run traps; logs commands for OTel / fail2ban / Docker
- crypto-emit-max-fires-cap-clear-2026-06-25 — symptom: container stuck logging
EMIT_MAX_FIRES reached, signal emission paused max_fires=Nat ~1Hz; diagnosis path (wrong hypothesis first — JetStream subject capture, ruled out via/jsz?streams=true&config=true); real cause (DB-hydrated guardrail + Pattern A residue); fix (TRUNCATE +systemctl restart); verification (boot logguardrail hydrated from DB hydrated=0, DB row growth, JetStreamlast_seqadvance) - pmv2-zurich-local-otel-store — 7-step break-glass for the local OTel store: missing
otelq/duckdb → re-applymonitoringrole (NOTmake deploy); empty/var/log/otel/→systemctl status otelcol-contrib+ journal; disk fill →rm /var/log/otel/*.jsonl.*keeps active files (perm change inroles/monitoring/defaults/main.yml);Permission denied→usermod -aG otelcol-contrib ops+ re-login; DuckDBBinder Error: Could not find key…→ OTel schema drift, patchFLAT_LOGSCTE inroles/monitoring/tasks/main.yml(never on VM); restore SigNoz/OTLP vendor → dual exporter inconfig.yaml.j2alongside file exporters; config source of truth =ansible/roles/monitoring/
Workflow
- spec-1-retrospective — two-stage subagent review (spec-compliance + code-quality) caught four design-stage bugs; carryover recommendations for Spec 2