Skip to content

Releases: 42U/laqrumcode

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 17 Aug 00:28

Security-hardening release: a full-repo security & privacy audit (9 findings, all remediated — report with locked hypotheses and appended outcomes archived on the operator share), the retirement of the legacy root:root database credential, and the completion of PR #22's soul-pipeline rescue.

Security

  • Full-repo audit fixes (LAQ-SEC-001…009). The web-UI launch flow no longer exposes the master hook-API token via the opener's argv, terminal output, or browser history — /ui/auth now accepts only a single-use 60-second nonce minted over the Bearer-authed loopback API (POST /ui/mint), and master-token-in-URL is rejected outright. daemon.log/auto-drain.log open at 0600 (with convergence chmod for existing installs; rotation keeps generations tight) since warn-level lines can quote graph content. Backup dumps write into 0700 directories with 0600 files. Both loopback HTTP surfaces reject non-loopback Host headers (DNS-rebinding defense-in-depth; shared policy in src/shared/net.ts). Production npm tree audits clean (hono 4.13.2, fast-uri 3.1.5, body-parser 2.3.0, tar 7.5.22 — all transitive; one dev-only Windows-only esbuild LOW accepted under vite's pin). drop-test-namespaces is now a Node script using the admin credential file (the old curl -u root:root was dead post-hardening and leaked credentials via argv); setup.sh stops suggesting curl | sh. Audited clean with no findings: the SQL-interpolation surface, auth design, web-UI serving, download pinning, and multi-user isolation.
  • root:root retired (Phase 3 credential chain). External SurrealDB targets resolve credentials as: explicit SURREAL_USER/SURREAL_PASS verbatim → the managed per-user cred file → legacy root:root last resort. Discovery tries the chain per candidate port and propagates the winner, closing a split-brain hazard where a hardened instance (rotated root) looked like "not a laqrumcode DB" and triggered a fresh managed spawn. scripts/provision-scoped-users.mjs defines per-project root-level EDITOR users (full data + schema DDL, no user management) converged with the managed cred files; scripts/rotate-root-cred.mjs rotates root to a strong secret with persist-before-rotate ordering and prints rejection receipts for the old default. All maintenance scripts share the same resolution (scripts/surreal-cred.mjs); live-DB tests resolve identically and pass against hardened and unhardened instances.

Added

  • Redaction covers database/service credential shapes (GH #16 extension): HTTP Basic-auth header values, *_PASS/*_PASSWORD/*_TOKEN/*_SECRET/*_API_KEY= env-style assignments (placeholder-aware), user:pass@ URL credentials, and quoted JSON pass/password/token/secret values — with false-positive guards pinned by tests (MAX_TOKENS=4096, $VAR indirection, <placeholder> forms stay untouched).
  • scripts/scrub-stored-secrets.mjs: stdin-fed (never argv), receipt-printing retro-scrub that replaces known secret literals across turn/turn_archive/memory/concept/monologue with the standard placeholder. The audit's own scrub run found zero stored matches (positive-control verified) — prevention landed with nothing to clean.
  • README Security & privacy section documenting the threat model and the hardening toolchain.

Fixed

  • PR #22 follow-through (soul pipeline). Merged charleshmmnd's soul_evolve earned-values fix and closed the gaps around it: the evolve prompt embeds a partial schema (nothing required) with explicit replace semantics; a delta-guard merge appends instead of letting delta-shaped returns wipe stored sections; tolerant per-section coercion (bare strings / alias keys / single non-array values) with a junk guard covers both soul handlers; a landed evolution re-seeds the Tier-0 soul entries (previously graduation-only, so evolution was invisible at runtime); sections_revised counts only confirmed writes.
  • Soul pipeline hardening wave 2: stale soul_generate zombies self-complete once a soul exists (fetch gate + actionable-count gate + graceful soul already exists commit outcome — previously each straggler burned a full synthesis then failed); per-section caps (20/10/20/10, mode-aware, logged) and a 50-entry revisions trim bound soul growth; reviseSoulGuarded closes the concurrent-evolve lost-update race with a value-CAS single-shot write (probed live: += array concat, UPDATE-on-missing no-op, exact array-of-object guard equality); seedSoulAsCoreMemory is create-first with prefix-paired archiving (a crash can no longer leave Tier-0 without soul entries); tri-state createSoul kills the double-graduation-event race; SoulDocument.emotional_dimensions says description, matching schema and writers.

Known follow-ups

  • The daemon-gated R6 TCP spawn integration test cannot run on a machine whose live daemon owns the socket (it verifies singleton-spawn semantics); it remains the only red test in that environment.
  • One dev-only npm LOW (esbuild under vite 7.3.5, Windows dev-server) awaits a vite major.

v0.8.7

Choose a tag to compare

@github-actions github-actions released this 04 Aug 14:18

Added

  • The daemon now replaces itself when its connection-level self-heal proves futile. Production incident (2026-08-02 → 08-04): every query blew the 60s deadline, every zombie-reconnect "succeeded" (fresh Surreal, connect + signin round-tripped), and the next query died again — for two days — while an identical fresh process against the same server answered in under a second. The 0.7.118 self-heal kept rebuilding the one component that wasn't broken (the connection) and had no feedback that its medicine wasn't working; hooks failing open at 50s made the outage silent, and the valid pid file blocked every replacement spawn. A WedgeDetector per store now tracks the streak of query-deadline timeouts and futile completed reconnects; when a streak crosses 10 timeouts and 3 futile heals and 3 minutes with zero successful queries (any success resets everything — a merely-slow server can never escalate), the store concludes the fault is process-level state a connection rebuild cannot reach and hands off to the daemon's gracefulCleanup, whose existing 8s watchdog guarantees the exit even when the wedged store hangs the drain. The pid-file spawn guard then brings up a clean process on the next hook/MCP demand — turning this failure class from a multi-day silent outage into a ~minutes blip. Both the primary and the dedicated maintenance store escalate; standalone embedders (mcp-server.ts, tests) stay log-only since a library must not process.exit(). Knobs: LAQRUMCODE_WEDGE_STREAK_MS (floor, clamped 30s–1h) and LAQRUMCODE_WEDGE_EXIT_DISABLED=1 (log-only escape hatch). test/wedge-escalation.test.ts (11) pins the thresholds, the reset-on-success semantics, the single-burst floor, the fire-once latch, and the shutdown/disabled suppressions.

v0.8.6

Choose a tag to compare

@github-actions github-actions released this 26 Jul 20:03

The loop guard finally works, and the release script can no longer lie about what it bumped.

Fixed

  • The planning gate now fires on silence, not on volume of work. It keyed on session.toolCallsSinceLastText — the right quantity — but nothing ever reset it: the only code that zeroed it on assistant text (engine/hooks/llm-output.ts) is imported solely by a test and is not among the hook methods the daemon registers, and Claude Code's hook surface has no assistant-text event at all. So it tracked toolCallCount exactly and interrupted long, legitimate investigations to "summarize progress". The count is now derived from the transcript (countToolCallsSinceText) rather than tracked in memory, which also makes it correct across daemon restarts, relay reconnects and session-map eviction. Two structural facts were verified against a live transcript rather than assumed: Claude Code writes each content block as its own JSONL entry, and the entry carrying a tool_use is flushed before the tool runs. thinking blocks deliberately do not reset the count (thinking happens inside loops too), and tool_result blocks — which arrive as user-typed entries — do not either, since treating them as user input would reset on every call and disable the gate. Fails open: an unreadable transcript keeps the gate quiet. Measured live mid-session: old predicate 26 (fires), new predicate 2 (quiet). Closes #20.
  • scripts/bump-version.sh missed surfaces and printed a green summary anyway. package-lock.json (both refs) and src/mcp-server.ts were never bumped, and its staleness check only verified surfaces it already knew about — self-fulfilling, since it cannot discover a surface nobody wired in. v0.8.5 shipped with two stale surfaces because of this. The script now bumps all seven surfaces (package-lock via node, never a blanket sed that would corrupt dependency pins), verifies each explicitly, and then sweeps the repo for the old version, failing on anything still carrying it — so an unknown surface breaks the run instead of passing quietly.
  • bump-version.sh no longer touches git by default. It ran git add -A && git commit && git tag unconditionally, which on v0.8.5 swept ten unrelated fixes into chore: bump to v0.8.5 and violated QA-BEFORE-BUMP. --commit and --tag are now opt-in, and --commit refuses when the working tree holds changes outside the version surfaces. Adds --skip-tests (the README badge ran the full suite on every bump) and semver validation of the argument.

Added

  • test/loop-guard-transcript.test.ts (16) — synthetic transcripts, no database, runs in CI. Verified fail-on-revert.
  • test/tier1-legacy-migration.test.ts (2) — exercises the v0.8.5 legacy tier-1 migration against a live throwaway database, because string::starts_with throws on a NONE session_id and queryExec swallows it, so a missing guard would make the migration a silent no-op that still reports success.

v0.8.5

Choose a tag to compare

@github-actions github-actions released this 26 Jul 18:50

Directive delivery and daemon stability. Two of the fixes in this release correct regressions introduced earlier in the same release cycle and caught by the pre-release QA waterfall before any tag existed — the notes below describe net behaviour against 0.8.4.

Fixed

  • Tier-0 directives were silently truncated at 800 chars. MAX_CORE_MEMORY_CHARS applied a flat per-item guillotine with no signal to anyone. Because importance correlates with length and truncation eats the tail — where a rule's concrete specifics live — this preferentially destroyed the most important directives. On a real 25-entry install, a majority were being cut mid-clause every turn while ~4k chars of the tier-0 budget sat unused. perItemCapFor() now scales the cap with priority, and applyCoreBudgetVerbose() fits entries in two passes: admission at the flat floor first, then leftover slack spent raising caps highest-priority-first (partial upgrades allowed). Nothing is dropped that would have fit before, nothing gets less room than before, and the budget is actually used. Measured on the same install: 25/25 entries injected, 0 dropped, full budget consumed. (#19)
  • TIER0_MAX_TOTAL capped tier 0 by entry COUNT, which is the wrong unit — entries vary by an order of magnitude in size, so a count cap refuses writes while under budget and permits sets that overflow. Admission now tests the candidate against the same character budget the renderer enforces, and refuses with something actionable (budget_full names the weakest entries; would_evict names only what the candidate would actually displace, diffed against a baseline so pre-existing overflow is reported separately rather than blamed on the new entry). The update path is checked too, not just add. (#19)
  • The injection envelope was deleting its own section tags. wrapMemoryContext ran stripStructuralTags over the fully assembled context, and recalled_memory / active_directives / session_directives / reflection_context are all on that list — so the labels laqrumcode had just written were stripped on the way out and tier-0 and tier-1 directives reached the model as one unlabelled run of bullets. Breakout protection moved to the content (retrieved nodes, core-memory text and category, skill and reflection fields, resurfacing memories, wakeup briefing, compaction summary, soul summary), where the wrapper tags survive; stripReminderWrapper now removes only <system-reminder>. (#21)
  • stripStructuralTags was single-pass and defeatable by nesting — deleting an inner tag splices the outer halves into a live one, so depth-N nesting needed N+1 passes. Both strippers now run to a fixpoint (guaranteed to converge, since every effective pass strictly shortens the string) and neutralize any residue if the safety bound is ever reached.
  • Tier-1 "session-pinned" directives were pinned to nothing. getAllCoreMemory had no session filter and nothing retires the rows, so every session directive ever written loaded into every later session — including entries whose own text says "this session". Root cause was deeper than the read path: the MCP relay invented mcp-client-<pid> as its session id while the hook path keyed on Claude Code's session UUID, so the two id spaces never intersected. The relay now sends CLAUDE_CODE_SESSION_ID (resolveSessionId(), with an explicit LAQRUMCODE_SESSION_ID pin still winning for auto-drain), unifying the spaces; tier-1 reads are scoped to the asking session; and a one-shot migration retires legacy mcp-client-* rows explicitly rather than letting them go quietly invisible. This also fixes injectedSections invalidation (it was clearing the cache on the session that does not build the prompt) and makes the per-session tier-0 write cap survive a relay restart. (#21)
  • archiveOldTurns could stop draining silently. The anti-join ran in-DB with LIMIT applied after the membership filter, so the whole backlog paid an O(stale × referenced) per-row test and crossed the 8s TIMEOUT once it grew. Rewritten as indexed scans plus a pure O(stale + referenced) Set anti-join (selectUnreferencedTurns), now paged rather than capped at a single window — referenced turns stay candidates forever and pile up at the head of the scan, so a fixed window eventually yields nothing on every run. The job also records a status='error' row on failure so the 30-minute backoff engages instead of re-firing the full scan every boot, and warns when a non-empty candidate set yields nothing archivable.
  • Bootstrap maintenance no longer re-hammers the database during a daemon flap — deferred and gated on the daemon having retained an IPC client, so transient cold-boot daemons skip it.
  • Daemon spawn-lock race. The holder pid is now stamped at O_EXCL acquisition, so a racer can no longer read an empty lock (Number("") === 0), judge it dead, and spawn a second daemon concurrently.
  • Reconnect thundering herd — jittered backoff before an mcp-client reconnect so a single daemon exit doesn't make every relay re-handshake in the same instant.

Changed

  • The tool-call planning gate keys on toolCallsSinceLastText rather than total tool calls — the signal that separates a loop from productive work. Documented as inert: nothing in the production hook path produces that signal today, so it fires exactly where the old predicate did. See #20.
  • The softInterrupt block message no longer claims the user pressed Ctrl+C; nothing sets that flag from a real interrupt.

Known follow-ups

  • #20 — wire an assistant-text producer so the loop guard can actually distinguish looping from work.
  • Age-based archival of orphaned tier-1 rows (hygiene; read-scoping is the correctness mechanism).
  • retrieval_outcome id prefetch in archiveOldTurns is unbounded and re-Set per page.
  • Integration tests gated on a live SurrealDB return early rather than skipping, so they report green in CI, which provides no database.

v0.8.4

Choose a tag to compare

@github-actions github-actions released this 27 Jun 18:04

The dense+sparse hybrid retrieval arm — and the correction of an earlier wrong "infeasible" call.

Added

  • Hybrid dense+sparse retrieval (BM25 full-text + 3-way RRF). A lexical/sparse arm now joins the dense vector arm and the keyword/tag arm in the injection pipeline. schema.surql defines a fts_analyzer (blank/class/camel/punct tokenizers + snowball English stemming) and FULLTEXT BM25 indexes on concept.content, turn.text, memory.text, artifact.description, skill.description. SurrealStore.fulltextSearch() runs a per-term @n@ OR query (recall) ranked by summed search::score, unioned into the candidate pool and folded into the graph-seed Reciprocal Rank Fusion. This adds recall of exact-term / rare-token / code-identifier rows the dense embedding misses entirely. (BGE-M3's sparse heads aren't reachable via node-llama-cpp, so the sparse arm is SurrealDB-native BM25 — no model sparse head needed.)
  • Offline retrieval benchmark (scripts/retrieval-benchmark.mjs) — NDCG/MRR/Recall over the labeled retrieval_outcome table, to gate ranker changes with a number instead of a guess.
  • scripts/drop-test-namespaces.sh — durable cleanup for the vitest temp namespaces (kctest_*) that accumulate on the local SurrealDB instance.

Changed

  • Managed SurrealDB binary pinned 3.0.5 → 3.1.4 (bin-manifest.json) — aligns fresh installs with the version everything is tested against. Deliberately not 3.1.5: issue #7383 is a 3.1.5-specific RocksDB OOM under full-text load that would regress the new hybrid. All 5 per-platform sha256 from the GitHub release digests; linux-x64 download-verified.

Notes

  • SurrealDB 3.x full-text BM25 uses FULLTEXT ANALYZER (renamed from 2.x SEARCH ANALYZER in 3.0.0-beta). search::score returns 0 on a near-empty index (#7290 fresh-DB corpus-stats edge) but is correct at real scale — fulltextSearch() falls back to a term-frequency rank for the empty-index case. skill indexes description, not body (full skill bodies are large enough to stall the FULLTEXT build / schema-apply).

v0.8.3

Choose a tag to compare

@github-actions github-actions released this 27 Jun 15:17

A SOTA-retrieval optimization pass: deliberate recall() and passive injection now share the full ranking stack, the injected set is diversity- and hybrid-fusion-aware, the learned scorer trains on the right signal with hard-negative emphasis, and heavy maintenance can no longer stall hook serving.

Performance

  • recall() routed through the real ranking stack. The deliberate recall() tool ranked by raw cosine only — bypassing the semantic dedup + bge-reranker-v2-m3 cross-encoder that passive auto-injection uses, so explicit recalls were ranked worse than what an agent passively receives. It now runs dedup → cross-encoder rerank (0.6·cosine + 0.4·cross). Also fixes a default-limit mismatch (was 3; schema documents 5).
  • MMR diversification of the injected set. A Maximal Marginal Relevance pass (λ=0.7) between the cross-encoder rerank and token-budget selection — greedy argmax(λ·score − (1−λ)·maxCosineToPicked) over the selection-eligible items — so a redundant concept-family can't crowd out coverage.
  • Hybrid dense+keyword fusion (RRF). The dense (vector) and keyword (tag) candidate rankings were merged by plain id-concat. They're now fused with Reciprocal Rank Fusion (k=60) for graph-walk seed selection, so a keyword-strong concept with mediocre cosine can still seed expansion.
  • Hard-negative weighting in the ACAN training loss. The learned scorer's pointwise objective now upweights hard negatives (high retrieval cosine, low utilization) 2.5×. Weights version bumped 2→3 → clean cold-start retrain (ACAN falls back to WMR during the transition).
  • HNSW M=16 / EFC=200. Bumped the vector indexes from SurrealDB's M=12/EFC=150 defaults to the SOTA-minimum (recall is monotonic in M; extra RAM offset by F32). New installs get it automatically; existing installs pick it up in the scripts/migrate-hnsw-f32.mjs rebuild (now F32 + M/EFC together).

Fixed

  • daemon: Heavy maintenance jobs now run on a dedicated SurrealDB connection, so a maintenance deadline → zombie → reconnect can no longer reject in-flight hook queries (the architectural complement to 0.8.2's per-query TIMEOUT). Best-effort with a safe fallback to the shared store — cannot regress maintenance.
  • acan: The learned scorer trained on a recomputed aux-feature vector that didn't match the one used at scoring time (train/inference skew). It now persists and trains on the exact stage-time aux_features.

Known follow-ups (benchmark-/ops-gated)

  • True dense+sparse hybrid (BGE-M3 sparse/ColBERT heads) is blocked: node-llama-cpp exposes only dense embeddings, and SurrealDB BM25 full-text parse-errored on this build. A BM25 sparse arm + RRF-into-final-scoring needs a recall benchmark.
  • Full listwise RankNet ACAN loss needs offline NDCG/MRR validation before activation.
  • Aggressive HNSW (M 24–48 / EFC 300) and a Qwen3-Embedding swap (model path + dims are already config-swappable via EMBED_MODEL_PATH) are a benchmark + re-embed decision.

v0.8.2

Choose a tag to compare

@github-actions github-actions released this 27 Jun 05:13

Fixed

  • daemon: archiveOldTurns now caps its scan with a server-side TIMEOUT 8s. On an overloaded SurrealDB instance the maintenance query could otherwise blow the 60s client deadline → flag the shared connection "zombie" → tear down the socket that also serves hook memory-injection, the cause of intermittent "daemon unreachable". A SurrealDB TIMEOUT error isn't "deadline exceeded", so it also avoids the retry-doubling.

Performance

  • HNSW indexes: all 9 *_vec_idx vector indexes now pin TYPE F32 instead of SurrealDB's F64 default — lossless (embeddings are F32-precision) and ~halves in-RAM index size. New installs get it automatically; existing installs rebuild via scripts/migrate-hnsw-f32.mjs.

v0.8.1

Choose a tag to compare

@github-actions github-actions released this 27 Jun 03:56

Fixed

  • auto-drain: terminate the headless-extractor spawn args with -- so DRAIN_PROMPT is no longer swallowed by the variadic --disallowed-tools (claude 2.1.x). The bug made every drain exit 1 in ~6s with no queue progress — flipping the autoDrain maintenance job RED and leaving pending_work permanently undrained. Verified against claude 2.1.195 (live drain 6→4).

Changed

  • scripts/migrate-legacy-graph.mjs: scaled the legacy-graph migrator to 300k+ rows — keyset-paginated streaming reads (flat memory), bulk INSERT RELATION INTO for edge tables (~100x faster than per-record RELATE), and a resilient per-record skip+log fallback for rows the destination schema rejects. Preserves the content-safe verbatim-copy contract.

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 24 Jun 15:21

🔄 KongCode is now LaqrumCode

Same engine, new name — this is the rebranded marketplace ship. Because Claude Code identifies plugins by plugin@marketplace (no rename/alias path), existing installs need a one-time manual migration:

/plugin uninstall kongcode@kongcode-marketplace
/plugin marketplace remove kongcode-marketplace
/plugin marketplace add 42U/laqrumcode
/plugin install laqrumcode@laqrumcode-marketplace

Then restart Claude Code. MCP tools are now mcp__plugin_laqrumcode_laqrumcode__* — update any permission allowlists that referenced the old mcp__plugin_kongcode_* ids.

Moving an existing memory graph from ~/.kongcode? This release ships a content-safe migrator (copies your graph verbatim into the laqrum namespace; never rewrites your memory text):

LEGACY_BRAND=kong node scripts/migrate-legacy-graph.mjs

Old 42U/kongcode URLs 301-redirect here, so links and stars carried over.


Changed

  • Rebranded to laqrumcode — plugin id, marketplace name, MCP tool prefix (mcp__plugin_laqrumcode_laqrumcode__*), runtime paths (~/.laqrumcode), and SurrealDB namespace (laqrum). Existing installs must reinstall under the new id (see release notes).

Added

  • scripts/migrate-legacy-graph.mjs — content-safe migrator to import a pre-rebrand memory graph into the laqrum namespace (verbatim content copy + surgical structural-id fixups; LEGACY_BRAND/NEW_BRAND parameterized).

Hardened — enterprise 1M-install readiness pass (branch harden-1m-and-rename)

A 118-agent review (fan-out → triage → 3-lens adversarial verify) of the whole
engine for ~1M independent single-host installs surfaced 36 confirmed defects
(+ K0); 15 were refuted (one — orchestrator_metrics retention — because the
proposed hard-DELETE would have violated the Tier-0 NEVER-DELETE directive). All
confirmed defects are fixed below; full suite green (1318 tests). Frame: 1M users
= 1M local daemons, so the bar is deterministic correctness + per-host
resource/cost discipline, not server sharding. No content-table hard-deletes were
added; all new indexes are non-UNIQUE / IF NOT EXISTS (boot-safe).

  • K0 — drain pipeline unbroken. pending_work.status SCHEMAFULL enum was
    missing the committing transient that batch-2a's C1 CAS writes, so
    commit_work_results failed deterministically and NO work item could commit.
    Enum fixed; added test/schema-status-enum-drift.test.ts (non-mocked static
    guard for the whole code-vs-schema status-enum class — the mocked
    commit-claim-guard test couldn't see it).
  • Concurrency (CRITICAL/HIGH): K3 concept dedup race sealed via deterministic
    concept:⟨sha256(lowercased content)⟩ record id (no risky UNIQUE migration);
    K15 stale-recovery no longer reverts an in-flight committing row (clock reset
    at the CAS + pre-write ownership re-assert); K41 commit CAS made idempotent
    across a withRetry re-fire via committing_token; K31 causal_graduate claims
    chains at fetch time (RETURN BEFORE) so concurrent drains can't double-synthesize
    skills; K10 auto-drain lock no longer steals a live child's lock by age / unlinks
    a sibling's lock; K21 utility-cache running-average replaced with commutative
    util_sum/count accumulators; K9 consolidate mutual-archive guarded; K42
    createSoul idempotent.
  • Scalability hot path (CRITICAL/HIGH): K4 tagBoostedConcepts, K19/K20
    consolidate + concept-dedup, K18 reflection membership, K14 PreCompact reads —
    all converted off full linear cosine/WITH NOINDEX scans to HNSW-KNN /
    index-served bounded reads; K2/K23 retrieval-eval bounded + batched + moved off
    the Stop critical path; K6 deadline now cancels the inner pipeline (AbortSignal
    honored); K13 cross-encoder rankAll got a timeout + circuit breaker; K28
    graduation aggregations no longer run twice per prompt; K8/K29/K33 added missing
    created_at/timestamp/importance indexes + turn_score retention.
  • Resilience / resource (HIGH/MED/LOW): K1 dead session-reaper wired +
    hard sessions-Map cap; K48 _observedFilePaths cap + bounded scan; K11
    daemon close() drains in-flight RPCs before disposing the store; K12 RPC + embed
    backpressure; K5 ingest embed-failure degrades to an un-embedded (heal-able) row
    instead of dropping the turn; K16 concept backfill keys off content (not the
    dead name column); K17 embedding_cache periodic prune + pruned_at reset; K51
    memory backfill embeds the original short target; K32/K38/K39 added connect /
    readiness / download timeouts; K34 ACAN training uses transferable buffers; K40
    rerank all-dropped floor; K47 drain-log fd closed on spawn failure; K50 relay
    process-level rejection handlers; K35 UI search bounded.

Hardened — round 2/3 (loop-until-dry): regressions in the round-1 fixes + missed defects

A second adversarial review (regression audit of the fix commit + missed-issues
sweep) found 19 more confirmed defects — proof that one pass wasn't enough.
Most were regressions the round-1 fixes themselves introduced; two were genuinely
missed; three filings were refuted by running the real SurrealDB binary. All 19
fixed; full suite green (1407 tests).

  • Missed CRITICAL — Windows was entirely non-functional (R6): the MCP client
    spoke only Unix sockets (existsSync gate) while the daemon binds TCP on win32,
    so memory was dead on 100% of Windows installs and LAQRUMCODE_DAEMON_TRANSPORT=tcp
    was a no-op. Client is now transport-aware (TCP on win32 / env opt-in, fixed port
    18764), with a real meta.handshake readiness probe; dup-daemon protection preserved.
  • Missed HIGH — ReDoS on the shared event loop (R4/R16): the path-extraction
    regexes ([\w./~-]+\.ext) had catastrophic backtracking (~6s stall per
    PostToolUse/PreCompact on dot-heavy tool output). Replaced with one shared
    tokenizing extractor; K48's length cap alone didn't bound backtracking.
  • Regressions in round-1 fixes: R1 K21's deterministic-id muc UPSERT collided
    with the retained muc_mid_idx UNIQUE on legacy rows (froze utility writeback on
    upgrade) → removed the now-redundant UNIQUE + migration folds legacy rows; R2 K13's
    rerank breaker used a submit-time clock (tripped on queue depth) → single serial
    FIFO with dequeue-time clock; R5/R14/R15 K39's download used a wall-clock abort that
    killed healthy slow downloads + leaked/​hung on write errors → stream.pipeline()
    rewrite (connect-phase timeout only); R7 K1's cap evicted FIFO not LRU → recency
    bump; R8 K31 stranded the graduation backlog on a failed synthesis → bounded claim +
    un-stamp recovery; R9 K15's ownership re-assert was TOCTOU → markTerminal gates the
    terminal stamp on committing_token at write time; R10 K6 left staging/access-bump
    unguarded on abort; R11 K3's hash id broke on all-digit prefixes → constant letter
    prefix; R12 K16 healed concepts to a searchTerms-stripped vector → concept
    embedding_target; R3 K2's detached eval wasn't drained on shutdown → daemon
    pending-task registry awaited in gracefulCleanup.
  • Coverage gaps (R19/R20/R22/R23): added the real-behavior tests whose absence let
    the round-1 fixes ship under-verified (the mocked-test-blindspot class that hid K0).

Hardened — round 4/5 (convergence): regressions in the round-3 fixes + 1 pre-existing

A third review round (regression audit of the round-2/3 commit + whole-campaign
completeness critic) found 6 more (trend: 36 → 19 → 6, converging); all fixed,
full suite green (1451 tests).

  • S1 (pre-existing, HIGH): a failed schema apply left isAvailable() returning
    true (it checked only socket connectivity), so the daemon served writes for its
    whole lifetime WITHOUT the pending_work UNIQUE seal the dedup/committing_token
    CAS relies on. Added a schemaApplied gate (isAvailable = isConnected && schemaApplied), a bounded schema-apply retry, and a reconnect-path re-arm so a
    degraded store self-heals.
  • S6 (Windows multi-user isolation): R6's TCP path bound a flat shared loopback
    port, so a 2nd OS user on a Windows host adopted the 1st user's daemon + graph.
    Port is now derived per-user (username/SID hash offset, symmetric client/daemon),
    plus a 0600 per-user handshake token (loopback TCP isn't user-isolated like the
    Unix socket).
  • Regressions in the round-3 fixes: S3 — R4/R16's replacement left a quadratic
    suffix-strip regex and pre-compact lost its size cap → non-backtracking reverse
    scan (0.1ms vs 4.2s @64kb) + restored 64KB cap; S7 — R4/R16 silently dropped
    Windows backslash paths → backslash added to the token splitter; S4 — R9's guarded
    terminal stamp mis-reported a retry-idempotent success as skipped → self-token
    confirmation SELECT + stripped the contradictory skipped from the success
    envelope (a pre-existing ambiguity); S5 — R8's graduation un-stamp missed the
    stale-recovery path → stale-recovery now un-stamps won_chain_ids.

Hardened — round 6/7 (convergence): regressions in the round-5 fixes

A fourth review round found 4 more (trend: 36 → 19 → 6 → 4), all regressions
in the two most complex round-5 fixes (S1 schema-gate, S6 Windows transport),
fixed by hand; full suite green (1451 tests).

  • T1: S1's schemaApplied gate could latch a healthy daemon to
    isAvailable()===false permanently (a transient schema-reapply timeout behind
    the reconnect early-return) — worse than the bug it fixed. Made schemaApplied
    monotonic (the schema persists in the DB and is idempotent; reconnect only
    re-applies when never-applied) and only clears the zombie flag when usable.
  • T2: S6 enforced the handshake token on every connection but the co-located
    UDS client never sent it → self-lockout when LAQRUMCODE_DAEMON_PORT is set on
    Linux/macOS. Client now attaches its own 0600 token whenever readable.
  • T3: S6's per-user port window [18764, 28763] overlapped the managed-SurrealDB
    port 18765 → ~1/10000 use...
Read more

v0.7.130

Choose a tag to compare

@github-actions github-actions released this 19 Jun 05:16

Added — update_skill MCP tool

  • New update_skill tool revises an EXISTING DB-resident skill — the counterpart
    to create_skill, which deliberately rejects name collisions. Patches any of
    body / description / steps / preconditions / postconditions on the
    skill matched by name, and re-embeds so recall(scope="skills") reflects
    the new content. Before this, the only way to revise a shipped skill body was
    raw SurrealQL — and a naive UPDATE skill SET body = ... left the OLD embedding
    in place (the maintenance backfill only fills rows WHERE embedding IS NONE, so
    it never refreshes a stale-but-present vector), silently desyncing the vector
    index from the body. If the embedding service is unavailable the tool sets
    embedding = NONE rather than leaving it stale, so the backfill recomputes it.
  • Wired across all 5 MCP tool surfaces; test/update-skill.test.ts covers the
    re-embed, NONE-fallback, not-found, no-field, and short-body paths.

Fixed — ship the new tool's compiled output

  • v0.7.127–0.7.129 failed CI for one root cause: the release commits staged
    sources with git add -u, which skips NEW untracked files — so the compiled
    dist/tools/update-skill.js was never committed. CI runs the test suite
    against the committed dist/ (there is no prebuild step), so node dist/mcp-server.js hit a missing-module import, crashed on startup, and the
    mcp-handshake test timed out waiting for a response. The earlier "CI timing"
    and "contention" theories were wrong — re-running v0.7.126's workflow stayed
    green, isolating the cause to this release's diff. The dist file is now
    committed; release staging uses git add -A so generated output can't be
    dropped again. (update-skill.test.ts is a mock-based unit test, kept from the
    investigation — fast, no DB dependency.)