Show HN: Concord, some open source stuff
This PR addresses bare-metal deployment blockers, connection drops, LLM pipeline solidity, and ops durability for the Concord cognitive engine. It includes fixes for static assets, state saving, security vulnerabilities, and GPU memory management.
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]