Notifications You must be signed in to change notification settings
Fork 2
Star 9
Conversation
Copy Markdown
Owner
-->
claude
added 6 commits July 27, 2026 07:03
Wave 0: bare-metal deploy blockers — static assets, brains, tunnel, r…
d58b81d
…eboot survival
- startup.sh now copies public/ + .next/static into the Next standalone
bundle on every boot (pm2 runs standalone directly; without the copy every /_next/static/* 404s and the site renders unstyled)
- setup.sh + DEPLOYMENT.md: document ./startup.sh --runpod as the real
start path; pm2 alone requires --env runpod (default env block wires brain URLs to Docker hostnames)
- ecosystem.config.cjs: default env respects shell ALLOWED_ORIGINS/
COOKIE_DOMAIN instead of hardcoding concord-os.org; env_runpod enables CONCORD_SOCKET_ALLOW_POLLING_FALLBACK as WS-upgrade safety net
- cloudflared templates + setup-tunnel.sh: token var fixed to
CLOUDFLARE_TUNNEL_TOKEN (was CF_TUNNEL_TOKEN, which nothing read — tunnel silently never started); ingress rewritten for bare metal: /socket.io + /godot-ws + /api directly to backend :5050 (one proxy hop, WS upgrades survive), everything else to frontend :3000
- infra/systemd/concord.service: reboot persistence — pm2 resurrect
cannot restore the 5 Ollama brains; re-running startup.sh can
- bootstrap.sh: fresh-box provisioning (Node 20, Ollama, pm2,
cloudflared, base packages) → the honest two-script story
- runpod-cognition.sh: VRAM fit gate now actually runs on the canonical
path (was only wired into runpod-up.sh); embedding model moved to the UTILITY instance with OLLAMA_MAX_LOADED_MODELS=2 — it used to live on the conscious instance where every embed call evicted the resident ~9GB 14B model (cold-reload ping-pong on the chat hot path)
- server.js: embedding probe order prefers CONCORD_EMBED_OLLAMA_URL,
then utility; conscious instance is last resort
- .env.runpod: CONCORD_EMBED_OLLAMA_URL → :11436 (utility)
- .env.example: bare-metal DATA_DIR/DB_PATH guidance; document the
localhost:11434-11438 port map vs docker hostnames
- Deprecated (fail-loudly stubs): pull-all-brains.sh (re-pulled the
license-encumbered llava:7b), start-concord.sh, start-pinned.sh, backup.sh, warm-models.sh
Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01S33herYGdAwwdRgSeLFvyo
Waves 1+2: connection-drop root cause + security hygiene
fc600e4
Backend responsiveness (the "connection keeps dropping" chain):
- Removed the UNCONDITIONAL 2-minute full-state saver that defeated the
mutation-seq-guarded 5-minute safety net — a ~28MB serialize + SQLite row rewrite + WAL checkpoint every 120s on an idle box (~40GB/day of disk writes, recurring event-loop stalls)
- saveStateDebounced: 250ms trailing debounce → 5s LEADING-window
coalescing (env CONCORD_STATE_SAVE_DEBOUNCE_MS). Trailing debounce could starve under sustained mutation; leading-window bounds unsaved- mutation age at the window and cuts steady-state serializes ~20×
- saveStateCritical: rate-limited (2s window + trailing catch-up sync)
so a burst of marketplace purchases doesn't do one full 28MB serialize EACH; durable money/DTU stores are their own SQLite tables
- Removed forced global.gc() from both save paths (full sync GC right
after allocating a 28MB string doubled the stall; the memory-pressure watchdog already does rate-limited GC)
- saveStateSync now syncs the periodic saver's bookkeeping so an
already-persisted state is never re-serialized 2 minutes later
Godot gateway: ping reaper now tolerates 2 missed pongs (~50s, matching socket.io's 60s budget; env CONCORD_GODOT_HEARTBEAT_MS / CONCORD_GODOT_MAX_MISSED_PONGS). The 1-strike ~25s reaper also had a false-kill mode: after an event-loop stall Node runs expired timers before draining socket I/O, so buffered pongs looked missing and every Godot client was terminated on every stall.
Frontend connection truth:
- ConnectionStatus banner now keys primarily off the socket's confirmed
onConnectionLost/onReconnected lifecycle — the /health poll alone was decorative behind any proxy that answers /health itself (the nginx config did exactly that)
- CoopPanel + CouncilTheaterPanel: replaced ad-hoc same-origin io('/')
sockets (documented-broken through the Next rewrite — WS upgrades don't proxy) with the shared singleton; listeners detach on unmount
- useSocket: removed dead reconnection options (singleton hardcodes
Infinity) that sat in the effect dep array; fixed stale comments
- world lens: all 6 raw setInterval polls → useSmartPolling
(visibility-paused + jittered; immediate refetch on world change kept)
Security:
- runJob no longer grants internal-context privileges to user-enqueued
jobs: jobs.enqueue accepts arbitrary domain.macro kinds, and the runner used to execute them inside makeInternalCtx + stamp the actor internal:true — letting any authenticated user route any macro through the job queue to bypass internal-only gates (e.g. the council-gate skip). User jobs now run with exactly the user's own authority
- CONCORD_RATE_LIMIT_BYPASS hard-disabled in production (single env var
disabled every rate limiter)
- /metrics token-gated in production (CONCORD_METRICS_TOKEN; 404 when
unset) — nothing scrapes it on bare metal, so it was pure surface
- Stripe webhook paths exempted from both rate limiters (signature-
verified + idempotent; Stripe retries burst from rotating IPs — a 429'd webhook means a paid purchase never mints)
- Fixed the stale "RATE_LIMIT_MAX (300)" comment (real default 6000,
deliberately coarse per its declaration rationale)
Triage note: the money-txn detector highs on wagers.js / ledger.js / stripe.js are stale or the documented control-flow-blind FP class — wagers.js is already fully transacted (pinned by wagers-atomicity tests, verified green here); ledger/stripe "two writes" are mutually-exclusive try/catch fallbacks. Frontend tsc + eslint verified green with deps installed (the audit's "30 errors / lint unrunnable" was a missing node_modules artifact).
Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01S33herYGdAwwdRgSeLFvyo
Wave 1.5: LLM pipeline solidity — kill the ping-pong, honor slots, re…
89e1e37
…serve chat capacity
The "AI keeps dropping" root causes on the A40 bare-metal deploy:
- Embedding model no longer evicts the 14B conscious brain (Wave 0 fix,
cross-referenced here): every embed call used to unload ~9GB of resident model, paying a cold reload on the next chat message
- num_ctx is now sent on EVERY Ollama call path (ctx.llm.chat,
callOllamaStreaming, callBrain incl. its tool-follow-up branch, callOllama, ollamaChat). Previously none of them sent it, so every call silently ran at Ollama's small 2k/4k default regardless of BRAIN_CONFIG.contextWindow (up to 32768 for conscious) — prompts were truncated past that point, reading as "the brain forgot what I told it." Capped via CONCORD_NUM_CTX_CAP for VRAM control.
- Corrected the VRAM budget for the num_ctx fix's real cost: KV cache
now legitimately scales with configured context instead of being silently near-zero. verify-resource-allocation.mjs's flat 1GB KV estimate is now context/params/NUM_PARALLEL-aware — re-running it against the real A40 numbers show the previous CONCORD_WORLD_VRAM_MB bump to 16384 (justified by a stale "~22GB idle" assumption) now narrowly OVER-COMMITS the card by ~100MB. Restored to 8192, which is what the honest math actually supports (~8GB real margin). Left OLLAMA_NUM_PARALLEL at 1 — the "idle VRAM" the audit flagged doesn't exist once num_ctx is honored, so it must not be raised.
- ctx.llm.chat now honors the requested
slotfor LOCAL routing (was
BYOK-only) — utility/subconscious-targeted work no longer burns the 14B conscious model; falls back to conscious when the requested brain is down. Streaming chat priority now varies with the resolved slot instead of always claiming CRITICAL.
- Streaming chat (the actual default chat UX) now goes through BYOK
(was local-Ollama-only, ignoring a user's own provider key) and through _llmQueue at CRITICAL priority (was a raw unguarded fetch — the queue-position UX shown to users described a queue this request never entered).
- _llmQueue reserves 1 concurrency slot for CRITICAL by default
(configurable via reserveForCritical). Priority ordering only affects DEQUEUE order — it does nothing for slots already filled. On a 5-slot bare-metal queue, a burst of background/vision work (up to 120s each) could previously fill every slot before a live chat request ever arrived, with no preemption. Verified with a live scenario test: a 6-item LOW burst now fills 4/5 slots, and a CRITICAL request enqueued mid-burst lands in Claude-Session: https://claude.ai/code/session_01S33herYGdAwwdRgSeLFvyo
Wave 2.5: ops durability — force-restart hung processes, honest SQLit…
6d40063
…e math, migration numbering, alert consolidation
- health-check.sh: pm2_restart_if_stopped only ever restarted a pm2 status
of stopped/errored. The real bare-metal failure mode is hung-but-online (event-loop-blocked on a slow checkpoint or a >30s heartbeat module) — pm2 never sees a crash, so nothing restarted, even though the caller had ALREADY confirmed the HTTP health check failed. Added pm2_force_restart_if_unresponsive with two safety rails: a grace period (skip force-restart if the process started within the last 10min — a fresh install's ~396 serial migrations must not get killed mid-run) and a cooldown (skip if already force-restarted within the last 5min, so a persistently-slow-not-hung backend doesn't get restarted every cron tick). Also extended disk-usage checking to a separate DATA_DIR/DB_PATH mount when it differs from root (df / alone is blind to a full data volume).
- Migration numbering: migrate.js's
^\d{3}_.*\.js$regex +slice(0,3)
parseInt + lexicographic .sort() silently breaks once a migration
number reaches 4 digits (this tree is at 396 — 4 of headroom). The regex
still MATCHES a 4-digit file, slice(0,3) mis-parses "1000_x.js" as
version 100, which compares as already-applied and is SILENTLY SKIPPED
— no error, no log line. Lexicographic sort also misorders at the same
boundary. Fixed with a shared listMigrationFiles() helper (variable-
width capture + true numeric sort) used by both the apply and rollback
paths, plus the companion name-strip regex. New isolated-tmp-dir
regression test proves both the old parse's failure mode and the fix,
without touching the real shared migrations/ directory (an earlier
version of this test wrote into that real directory and intermittently
raced platinum-migration-up-down.test.js, which enumerates it
concurrently under node:test's per-file-process default — fixed by
keeping the test fully isolated).
- SQLite pragma honesty:
db.pragma("page_size = 8192")ran AFTER
journal_mode = WAL on an already-open DB — SQLite's own docs are
explicit that page_size "cannot be changed while in WAL journal mode,
not even on an empty database," so this was a guaranteed no-op on every
boot. Removed, and recomputed wal_autocheckpoint (4000→8192 pages) +
corrected every comment against the REAL page size (4096, not 8192) so
the documented checkpoint-size intent (~32MB) is actually achieved.
- Fixed two hardcoded busy_timeout=5000 worker DB handles (world-shard.js
writer, heartbeat-executor.js reader) that were half the main writer's 10s default with no env override — a shard write racing the periodic state-save checkpoint had less grace than the writer itself before SQLITE_BUSY.
- Alert webhook consolidation: health-check.sh now falls back to
ALERT_WEBHOOK_URL (the var server/lib/error-alerting.js and monitoring/synthetic/critical-paths.js already use) when CONCORD_ALERT_WEBHOOK isn't set — one variable now covers both infra-level and application-level alerts. Documented in .env.example and .env.runpod (previously undocumented in either).
- db-backup.sh: DB path resolution checked the legacy $DATA_DIR/db/
subdir BEFORE the real $DATA_DIR/con
[truncated for AI cost control]