Skip to content

v1.2.0

Latest

Choose a tag to compare

@github-actions github-actions released this 12 Nov 17:18
· 54 commits to main since this release
03c4aed

New Feature

  • The reference extractor reads Parquet files: each file becomes a table whose rows are searchable passages cited by row, and a typed dataset. (#269)
  • knowledge.query calculates over the datasets ingested files become: a typed plan of filters, joins, groups and aggregates (or the rows themselves) runs over every row of the live snapshots, and the result carries a receipt naming each snapshot it read. (#278)
  • knowledge.synthesize takes datasets: the model proposes a knowledge.query plan from their schemas, the server checks and runs it over every row, and the answer cites the result as [knowledge.query#1]; when the task is ambiguous the answer abstains with the question to ask. (#279)
  • The TypeScript SDK sends a file for the server's extractor to parse: loom.knowledge.upload posts its bytes, loom.knowledge.waitForIngest follows the ingest job until it ends, and loom.knowledge.ingestFile does both around knowledge.ingest, from Node or a browser (Blob, File). (#281)
  • knowledge.formats lists every document format an ingest reads, the file extensions and media types that select each, and whether a configured extractor serves it, with the upload's size limit and the extensions it refuses, so a client can tell a person before sending a file that it would not be read. (#282)
  • loom knowledge migrate-originals moves the document originals still stored in ClickHouse to the S3-compatible bucket. Each one is hash-verified before and after the copy, and only then switched to the object. (#285)
  • The reference extractor reads scanned PDF pages, and pictures that cover a fifth of a page or slide, with OCR: a scan's headings, tables and text are cited by page like any page's, and a diagram's labels are searchable. Without OCR (LOOM_EXTRACTOR_OCR=off, or an image built with OCR=off) such a page or slide is reported not complete instead of read as if it held nothing. (#298)
  • The dashboard can erase or export everything Loom holds for a namespace or a user (Activity → Erasures, with each erasure's status and what it left behind), show which tokens read how much of a namespace and of one user's memory (Activity → Exposure), and forget every memory one agent wrote (Agents). (#346)
  • A deployment can keep live memory in Postgres (LOOM_STORAGE_AUTHORITY=postgres, LOOM_PG_DSN): point reads, compare-and-set and the conflict check's view of concurrent writes come from a Postgres transaction, while vectors, search and history stay in ClickHouse. ClickHouse alone stays the default. A deployment chooses the authority at its first boot, on an empty ClickHouse; one whose ClickHouse already holds memory refuses to start with LOOM_STORAGE_AUTHORITY=postgres, so it leaves the setting unset. (#158)
  • The Helm chart takes a postgres block that runs Loom with an external Postgres as the memory authority over verify-full TLS, and values-clickhouse-cloud.yaml with a guide sets up ClickHouse Cloud plus ClickHouse Managed Postgres. (#467)
  • Under the Postgres authority, loom reconcile --restored clickhouse|postgres --point <time> repairs a deployment after one of its stores is restored from a backup: the erasures and redactions the other store records past the restore point run again, and after a ClickHouse restore every revision and knowledge document committed since is published again, so nothing erased or redacted comes back. Knowledge chunks live in ClickHouse alone: the command names each source to re-chunk with loom rechunk (past 20 in a namespace, the namespace). (#460)
  • Under the Postgres storage authority, namespace.audit, namespace.validate, namespace.tune, memory.inspect and knowledge.inspect's decision counts and memory findings read memory once ClickHouse holds every write the namespace committed, and say so in skipped when it does not; a finding cites a row only while the authority still serves that version. (#391)
  • Under the Postgres storage authority, knowledge.inspect says in skipped how many document commits have not finished publishing to ClickHouse (owed and parked). It lists a document only while knowledge.get serves it at the listed version, so listed_complete is false whenever the document sections may be short. (#439)
  • Under the Postgres storage authority, knowledge ingestion jobs are kept in Postgres. A job's status and cancel read what was last saved, and a run that lost its job to another worker saves nothing more. (#447)
  • Under the Postgres authority, knowledge.inspect says how many ingestion jobs ClickHouse holds at an older version than Postgres, so the job counts and listed jobs are not read as current when they lag. (#466)
  • Under the Postgres storage authority, the knowledge dataset catalog (which snapshot serves each dataset, and its schema approval) is kept in Postgres. knowledge.dataset_approve commits only while the document and snapshot it reviewed still serve. (#465)
  • Under the Postgres authority, Loom reports how long a search waits for earlier writes to reach ClickHouse, how long a revision takes to become searchable, how many deliveries are owed or parked, and how many ClickHouse inserts its publishers make. GET /v1/health?include=postgres gains the same backlog, and the monitoring guide states the pipeline's SLOs and alerts. (#469)
  • Under LOOM_STORAGE_AUTHORITY=postgres with LOOM_PG_PAYLOAD_CLEAR_ENABLED, a memory revision keeps only its metadata in Postgres once every usable ClickHouse backup holds it. That happens past a horizon that is never shorter than LOOM_CH_BACKUP_INTERVAL_MINUTES plus 60. Before restoring a ClickHouse backup, turn the switch off; the steps are in backup and recovery. (#457)
  • Under LOOM_STORAGE_AUTHORITY=postgres, a memory.set of a key with no live head answers once Postgres commits, and each process publishes its commits to ClickHouse in shared flushes, one insert per table and month, on average one flush every LOOM_PG_PUBLISH_INTERVAL_MS (250 ms) with bursts of up to a second's worth, so loom.memory and loom.memory_embeddings get parts at that rate. (#433)
  • Under the Postgres storage authority, knowledge documents have their current version in Postgres. Two concurrent writes of one document cannot both publish or both take the same revision: the later one answers 409 PRECONDITION_FAILED, and a parked document delivery is counted in GET /v1/health?include=postgres (postgres.parked_knowledge_deliveries). Under that authority a namespace's retention_days does not expire knowledge documents or their chunks, and the server refuses to start with LOOM_RETENTION_DOCUMENTS_DAYS set. (#405)
  • Under the Postgres storage authority, knowledge.search, knowledge.outline, knowledge.synthesize and memory.stack serve only the chunks of each document's current version (a document written before its namespace had heads keeps serving every version ClickHouse holds): an older version still being retired, a deleted document whose delete is not yet published, and a write that has not committed are left out, and knowledge.search counts them in a new stale_dropped field. Under that authority knowledge.add and knowledge.bulk_add retire a source's earlier chunk versions, as knowledge.upsert does. (#441)
  • Under LOOM_STORAGE_AUTHORITY=postgres, a memory.bulk_set that writes one key twice does not hold the namespace's searches at their barrier, and a bulk write or import of a value the same identity wrote a moment ago is held by it as an exact duplicate. (#446)
  • Under the Postgres authority, the delivery worker decides a claimed search document from a durable read whenever the key's head is not the claimed revision, a deleted decision's record retirement reads its writers durably, and a knowledge job whose projection lags is sent again only from a durable read, so a failover never leaves ClickHouse with a row, or without one, because of a commit the new primary does not have. (#456)
  • The Review queue settles duplicate memories and documents (Duplicates tab, with the namespace-wide repairs), accepts or rejects facts on trial (Proposals tab), and suggests a decision for the conflict on screen. (#359)
  • Health shows the namespace's memory report, its search gates and its waiting extractions; a namespace admin applies expiry, re-embeds, removes an extractor's facts, rolls back, snapshots and restores there, and an org admin retries or dismisses projection failures. (#395)
  • The memory drawer can lock and unlock a memory, mark it as wrong, share it with every agent in the namespace, record whether it helped, redact a past version, and show the source turns it came from and the typed links into and out of it. Memory history rows carry version_text, the version as an exact decimal string. (#351)
  • Guardrails can delete the stored memories a pattern already matches, and an org admin sets the org's and a namespace's extraction rules and switches the org's model calls off or on there; Settings → Memory lists, adds and removes the namespace's memory webhooks and shows their deliveries. (#371)
  • The Library stops an ingestion while it runs, shows the typed tables a document's file became and approves their column types, and moves a collection under another; a repository's document source opens its settings, last sync and the documents it wrote. (#396)
  • knowledge.restore undoes a knowledge.delete or knowledge.bulk_delete: the document's last version and its chunks are searchable again. It never brings back what an erasure retired, and it says whether the stored original could be kept and, when it restores nothing, why. (#309)
  • Settings has a Structure tab: a namespace's record schemas and their records, its profile schema, its graph ontology and its categories, which an admin registers, approves and edits there and a writer adds categories to. (#400)
  • The dashboard renames and deletes conversation threads, grades a closed run, forgets a session brief, shows a namespace guide's history, badges a namespace someone just shared, shows the coding agents' model spend, and re-walks a repository branch from a commit. (#402)
  • insights.query (experimental) runs one read-only SQL statement over an allowlisted set of Loom tables, held to the token's org and namespaces by ClickHouse itself, and insights.catalog describes what it can read. A token with viewer on the namespaces can call both, and both join the assistant toolset. (#397)
  • With LOOM_ASSISTANT_LIBRECHAT_URL set, Loom runs LibreChat v0.8.8 headless beside it (the compose assistant profile, or assistant.enabled in the chart) and seeds one assistant agent at boot, whose tools are the assistant toolset and which recommends how a namespace should write its memories, grounded in the audit, the mentor and Loom's guides. (#418)
  • POST /v1/assistant/chat/completions asks the Loom assistant a question about a namespace as yourself, streaming or whole in the chat-completions format, and assistant.ask (over HTTP, MCP as loom_assistant_ask by name, and the SDKs' generated ops.assistant.ask / loom.assistant.ask) answers one question whole; each answer reads under a token delegated from yours and spends from the namespace's daily model cost cap. (#419)
  • Each Loom assistant answer comes back with its citations checked: a memory key the answer's own tool calls read, a document section Loom holds and a sql block the answer's own insights.query ran are each marked verified or not, with the reason and the key, source or path it names (ref), in a last loom.citations event of the stream and in a citations field of a whole answer and of assistant.ask. (#430)
  • The dashboard's Assistant page has a chat with the Loom assistant about the namespace, under your own access: the answer streams in, and under it its Sources list each citation as Loom checked it, a verified memory or document opening where the dashboard shows it, and an answer with no verified citation marked unsupported. (#444)
  • The Python and TypeScript SDKs stream a Loom assistant answer: agent.assistant.stream(...) and loom.assistant.stream({...}) yield its text as it is written, then the answer with its run id, token usage and the citations Loom checked; agent.assistant.ask(...) returns it whole, and SyncAgent iterates the stream blocking. (#443)
  • A tenant can read its memories, memory history, conversations and request ledger as SQL through the read-only loom.sql_v1_* views. The ClickHouse user that reads them is made by an operator with loom sql-access grant, scoped to an org and its namespaces. (#450)
  • erasure.status and erasure.request return the erasure's receipt: census, every substrate the final residue census counted with its count (zeros included), and purge, whether the saga deleted or tombstoned. The retention and erasure page shows how to re-count the memory and conversation tables through the SQL surface. (#462)
  • The assistant can read Loom's own documentation, as the running build ships it: help.search finds the sections that answer a question and help.get returns a page or one section. Both are read-only and on the assistant toolset. (#488)
  • A collection is created from a title: collection.create with derive_name derives its col-… name, and the Library shows titles. A name already used answers COLLECTION_NAME_TAKEN, which says whether a deleted collection held it and offers a free name. (#471)
  • An assistant answer about how Loom works cites the documentation it used as [help:<page> § <section>]; Loom checks each citation against the docs the server ships, and the dashboard links a verified one to a new Help page at that section. An answer that names an op or setting the server neither serves nor documents is marked unsupported, and the names are listed in unknown_names. (#498)
  • The dashboard's assistant chat is a panel the header opens on every page (docked beside the page on a wide window, over it on a narrow one, a sheet on a phone), and its conversations are kept in the browser, for the signed-in person only, for 30 days from last use. (#494)
  • Each assistant question names the dashboard page it is asked from in a chip the person can remove. An "Ask about this" button on a memory, a document and a Recall lab result opens the assistant with that item named. (#518)
  • The Library's Add document takes many files at once, picked or dropped: each becomes its own document, three are sent at a time, and a list says where each one is, with Retry failed and Stop. (#486)
  • The Insights tab and the Assistant page's panels are now starters in the assistant panel: each reads Loom's own card (inventory, audit, mentor hints, a subject's memories, documents, tune drafts) into the conversation, with or without asking the assistant. A streamed answer shows each tool call as a step while it runs and lists what it read under the answer. (#524)
  • The assistant panel offers Copy under every answer, Retry and an edit of the last question (also with the up arrow in an empty field), and "Ask again with sources" on an unsupported label. A streamed answer is shown done as soon as its text ends, with "Checking sources…" while its citations are checked. (#536)
  • A process with LOOM_CELL set serves only while its cell's rows in Postgres are read and accept its configuration; until then it answers 503 AUTH_UNAVAILABLE to everything but its health probes and runs no worker. With LOOM_CELL unset nothing changes. (#468)
  • A cell serves only the orgs it holds: a call for another cell's org answers 409 WRONG_CELL and writes nothing, and a token's own org new to the cell is placed through the router. With LOOM_CELL unset nothing changes. (#473)
  • On a cell, a CLI command refuses, before it runs, an org the cell does not hold. Every command that connects declares the orgs it writes, or why it writes for none. A cell needs the Postgres authority and authentication on every transport (cells). (#484)
  • loom router serve runs the router's placement API and org lookup from its own Postgres directory (LOOM_ROUTER_PG_DSN, loom router migrate). A cell places a new org through it, and a client finds an org's cell with GET /cells/lookup. (#497)
  • On a cell (LOOM_CELL), the first cell in recording records a new org only while the router's directory answers that the cell is attaching; while the directory is unreadable or answers serving, a new org answers 503 and the orgs the cell holds keep serving. The router answers that read at GET /cells/v1/cells/<cell>, for the first cell's own credential only. (#519)
  • The router answers a placed org's GET /cells/lookup from its cache while its directory cannot be read, and the lookup before any cell is registered answers 503 with the code NO_CELL, which clients match instead of the message. (#520)
  • The Python and TypeScript SDKs raise WrongCell (a PreconditionFailed) for the server's 409 WRONG_CELL, with .org and .cell naming the org and the cell that holds it, so a client can look that cell's host up with the router's GET /cells/lookup and call it. (#521)
  • loom login --org <org> (and loom.login(org=…)) signs in at the org's own cell, as the router's GET /cells/lookup names it. With no router, or no cell registered yet, it signs in at the server it was given; it waits out the router's 429 for at most --wait seconds (default 120); on any other answer it refuses without starting a sign-in. (#522)
  • loom router serve now routes every request that is not its own to one cell: a cell's own host to that cell, SCIM on the shared host to the first cell, a token-bearing request on the shared host to the cell that accepts its issuer, and everything else to the first cell (or, before one is registered, to LOOM_ROUTER_UPSTREAM_URL). Requests and answers are streamed through, with the client's host, scheme and address in X-Forwarded-*. (#523)
  • loom cell limit closes, reopens or bounds a cell to new orgs, loom cell credential makes a router credential (new) and rotates a cell's (--next, --finish, --revert), and loom cell which --org names an org's cell. limit and credential write the router's directory alone, through a new operator API (which reads the public lookup and sends no credential) the router answers with its operator credential, so they work while a cell is down. (#526)
  • Every deployment answers GET /v1/health/deployment with the deployment id it verified at boot, and LOOM_CELL_PROBE_PORT starts a probe listener that also answers the values under loom cell prepare's nonce keys in Postgres, ClickHouse and the document bucket, with the bucket and prefix it stores under. (#529)
  • The router registers a cell through its operator API (POST /cells/v1/operator/cells/<cell>/register, which loom cell prepare calls), once the cell's probe listener answers its deployment id and the nonces prepare wrote to its stores; the first cell is probed at the router's own LOOM_ROUTER_UPSTREAM_PROBE_URL. (#535)
  • loom cell prepare --cell <cell> --host <host> --credential-sha256 <sha256> registers a cell in the router's directory once the router has read back, through the cell's probe listener, the nonces it wrote to the cell's Postgres, ClickHouse and bucket, and then writes the cell's first, tenancy mode and own rows; it is safe to rerun. (#538)

Improvement

  • A search that serves excerpts (bodies="excerpt", the loom_search default) now shows a decision or constraint row as stored (the write-time clip: 1,200 characters, more for an extracted answer) instead of its first 200 characters. (#276)
  • loom decisions extract and the document-source sync read a design document whole and write each decision as a maintainer would, its outcome, reasons and rejected options in plain words, each claim cited by quotes of the document and checked against them before it is recorded. (#277)
  • dev/loom_ingest.py uploads a file's bytes and ingests them with knowledge.ingest, so the server's extractor parses every format and the CLI waits for the job and prints the revision it published; its --upsert flag is removed (every ingest publishes the source's next revision). loom demo ingests its file the same way. The Python SDK gains knowledge.upload, ingest, ingest_status, ingest_cancel, wait_for_ingest and ingest_file. (#280)
  • The dashboard's Library and a repository's documents add a file by uploading it for the server's extractor and following its ingest job to the revision it publishes. Each file is badged beforehand with the format this deployment reads it as, or refused when no extractor reads it; the browser no longer parses PDFs or Word files. (#283)
  • The dashboard's Library finds a passage inside its documents. Each hit is cited as an agent cites it: the document, the revision it was read from and where the passage sits, in lines, a key, pages or slides, or a sheet's rows. A hit opens its document. (#284)
  • In a DOCX with a Title paragraph, the reference extractor puts the document's headings under the title, so section paths and heading levels follow the document's outline. (#295)
  • The reference extractor reads a PDF as it is laid out: its tables become typed tables and datasets with rows cited by page, headings come from the type when the file has no outline, two-column pages read in order, sideways pages read like any other, and running headers, footers and page numbers are left out of the text and listed. (#292)
  • An XLSX header over two rows, a cell merged across columns grouping the names under it, names each column from both rows (2025 / H1, 2025 / H2); before, the second row of names was read as a row of data and the columns were 2025 and 2025 (2). (#308)
  • A PDF page or PPTX slide whose text is read but which shows a picture OCR does not read (one under a fifth of it, a picture fill or background), a chart, a diagram or an embedded object is now named in the artifact (attrs.images_unread) and in one warning, and a DOCX of text with pictures, charts, diagrams or embedded objects warns; before, such a file reported complete coverage and said nothing about the text inside them. (#301)
  • A gdrive source can sync the .docx files in its folders: name docx in its include (or tick .docx documents in the dashboard's form), and each .docx file goes to the extractor like a PDF or a .pptx deck, its original kept for download. The default include stays the four kinds it was. (#314)
  • Document sections that are meeting notes, status reports or bookkeeping, and record no decision, are labelled noise at ingest. A noise hit ranks below every knowledge hit close to the query and stays out of context.pack and memory.stack; a decision written in meeting notes is still served. LOOM_KNOWLEDGE_NOISE_FILTER=false turns this off. (#293)
  • code.index_status reports a dashboard reach kind: whether the dashboard calls an operation. (#315)
  • The assistant panel's inventory starter shows what the namespace holds and who appears in it; each user and agent there is a button that shows what it remembers, so no id has to be typed. (#306)
  • The Claude Code and Codex plugins name the oldest Loom server they support, so a later release can warn a session started against an older server, in one line, and the loom-status check reports the same. GET /v1/health reports the server's version. (#328)
  • An extractor's artifact whose line or character range ends past the uploaded original is refused, and the ingest job fails invalid_artifact. Each ingestion fault now has a test showing the job fails with its error code while the earlier revision keeps serving. (#368)
  • Meeting notes written by a meeting tool from a recording are now labelled: their transcript, invite header, next steps and footer rank below close hits and stay out of a context pack, while their summary and topic sections stay content. A stated plan ("we'll enable the cache by default", "the plan is to ...") now counts as a decision. (#344)
  • An HTML data table is extracted as one block, a row per line with its cells joined by a pipe, as a DOCX table is; before, each cell was its own paragraph, so a row's values were cited apart from each other. A table that lays out a page (a heading, list, code block, table or second paragraph in a cell) still reads as the blocks its cells hold, and a table of one column of text, unlike a DOCX one, is a paragraph without attrs.table. (#302)
  • A code-index tier that keeps failing on a repository is now recorded with its error kind and the one fix, and code answers, code.index_status and code.repos name it until the next pass that succeeds clears it. (#347)
  • Model-call spans carry the OpenTelemetry GenAI attributes as the conventions now name them, so HyperDX's LLM view and Langfuse read Loom's provider, operation, token split and cost as they are. Langfuse no longer counts a prompt-cache read twice in a generation's total, and an embedding call is a Langfuse embedding observation. (#392)
  • A code-index tier that keeps failing is a code_tier_failing problem in /v1/health, the dashboard banner and loom doctor, naming the tier, its error kind, since when, how far behind and the fix; the repository card names the branch and shows the same. (#387)
  • Every span Loom sends to Langfuse carries the request's user, session, tags and trace name, so Langfuse 4's user and session views and its observation-level evaluations see whole traces. Search stages arrive as retriever observations, which also puts a search on Langfuse's agent-graph view. (#390)
  • Failing code-index tiers are exported as the loom.code.tier_failing and loom.code.tier_lag_commits gauges, with an alert on the "Loom — Code Plane" dashboard and an optional PrometheusRule in the Helm chart (alerts.prometheusRule.enabled). (#398)
  • The assistant panel's Memories, Documents and Improvements starters, and the inventory's missing-count reasons, describe a namespace, its findings and its drafted changes in plain words; the exact op, setting and command names a fix needs come in a new refs field on memory.inspect views, knowledge.inspect findings and decisions and namespace.tune proposals, which the panel lists under "Exact names". (#361)
  • Each release's install.sh installs that release, so a pinned or saved copy installs the same version every time, and a download cut short runs nothing. Every release rehearses the one-line installer in clean Debian, Ubuntu and Alpine containers on amd64 and arm64 before it publishes, then checks the published release from outside. (#345)
  • Every error with a code, over HTTP and MCP, now carries category (input, auth, capacity, dependency, configuration or internal) and retryable, and the SDK errors expose both. Over MCP, a refusal of a namespace the token may not read adds a next step: check the name against namespace.list_mine before requesting a grant. (#303)
  • Each release attaches agent-setup.txt: the steps a coding agent follows to install that release on its own machine and prove it works, pinned to the release's version and commit, with content-level checks over real ops and a fixed PASS/FAIL report. /v1/health on a released image now reports the commit it was built from. (#411)
  • A fresh installer stack now searches by meaning with no key and no network: the server image carries a small local embedding model (bge-small, CPU), pinned to one revision and checked by sha256, and the stack's .env selects it. An existing install keeps the embedder its .env names. (#410)
  • A code-index tier that fails on a permission error naming a file now records the file and a permission kind whose fix names LOOM_CODE_REPOS_SHARED_GID. Before, the record held only PermissionError, or a harness tail classed as an indexer failure. (#429)
  • When the TypeScript compiler tier cannot install a repository's dependencies, its tier-health record holds the installer's name, exit code and the end of its output. Before, it held only RuntimeError. (#432)
  • An assistant answer's own token, or any token delegated from one, can no longer ask the assistant: assistant.ask and POST /v1/assistant/chat/completions refuse it with ASSISTANT_SELF_CALL (403). (#480)
  • When setup cannot write the analyst user, or a probe gets an answer it did not expect, /v1/health's analysis_user_failing problem names ClickHouse's error code and error name and, for a cause Loom knows, what it means and how to fix it. (#472)
  • Each assistant answer now comes back with support (supported, unsupported, unchecked or none), and the dashboard labels it in words. A greeting, a capability reply or an abstention carries no label, where it was marked unsupported before. (#476)
  • The assistant's refusals carry a detail.reason a client can branch on (not set up, auth off, starting, failing, an org-wide, narrowed or short-lived token), and /v1/health/ready reports an assistant check where one is configured. (#485)
  • The code_tier_failing health problem now gives each failing tier's reason and fix, and leaves out a lag of 0. loom doctor also names each entry it lists (up to five, then it points to code.repos) as repository@branch (org/namespace), with the error line that says why; a platform admin's /v1/health (the dashboard banner included) does so for each listed entry that is not restricted. (#470)
  • Dashboard errors say what happened and whether trying again can help, without guessing a cause. A refusal that is not an edit race no longer reads "Out of date — another writer changed this", a setup problem no longer says "try again in a moment", and a failed read says why it failed. (#474)
  • A compiler-tier delta is applied a round of files at a time, so the memory its read costs is one round, bounded by LOOM_CODE_SCIP_DELTA_ROUND_MAX_NODES (about 2.5 GiB resident in the worst case at the default, and the apply's rows for that round about as much again), plus a small record per entry that the node cap bounds, not the whole delta. A large tree's full run can raise LOOM_CODE_SCIP_DELTA_FILE_MAX_MB and LOOM_CODE_SCIP_DELTA_MAX_NODES without raising the worker's memory. (#478)
  • A token limited to a list of namespaces now records where it saves: the token form's Saves memories to picks a memory namespace of the list the token may write, the server signs that choice into the token, and switching namespace on the dashboard no longer moves where the token saves. A list token can therefore search a shared namespace first and save to another. (#496)
  • The assistant answers "how do I…" for the person asking: it knows the kind of credential they ask with and the role it has in the namespace, and answers how that role is raised from Loom's documentation. A question that does not plainly ask how to use Loom is searched in the namespace's memories and documents and in the docs together, so a fact the namespace holds is answered from the namespace. (#509)
  • The Library shows where a collection sits: its path from the top, each ancestor a link (on a phone, one back button), and a Collections section listing the collections inside the one shown (or the top-level ones on My files and a namespace). (#487)
  • An assistant answer whose sources were not all found in Loom now leads with how many were ("Partly confirmed: 2 of 3 sources found in Loom", "Couldn't confirm: 0 of 2 sources found in Loom"), and each source reads "Found in Loom" or "Not confirmed" with its reason. (#516)
  • The dashboard no longer shows a collection as a namespace: the namespace switcher and Browse list namespaces only and point to the Library for collections, header search and the copy dialog list namespaces only, the token form, Flow and admin Users show collections in their own group by title and path, and an account that reads only shared collections opens on the Library. (#515)
  • The Library lists the collections shared with you in their own "Shared with you" section, each saying where it sits. With no namespace at the top, it opens your own files instead of an empty page, and the new-collection dialog says that a top-level collection belongs to the org. (#532)
  • The Claude Code and Codex plugins call the conversation-recording control by its dashboard name, the Record conversations switch on the dashboard's Conversations page, including in the session line they print when recording is off. (#528)
  • The dashboard opens on Memories, and a new account sees a short Getting started checklist there: connect an agent (the token form opens in place), its first memory, and, for whoever can set them, the models. Each step ticks from what Loom holds: a token's first call, an agent's memory, the memory count. (#537)
  • Every empty page in the dashboard now says what will appear there and offers the action that fills it: "Connect an agent" opens the token form in a dialog in place, and the text names no API operation or server setting. (#540)
  • loom install --client cursor writes Cursor's MCP entry on the coding toolset, as the dashboard's Connect dialog and the token setup already do. --toolset still picks another listing. (#548)
  • A dashboard control your role cannot use is now shown with what it needs ("Needs writer on . An admin of it can grant that.") instead of being hidden, in the members dialog, the namespace settings, Structure, the Library, the memory drawer and the assistant's cards. (#544)
  • Plugin marketplaces, the MCP Registry and the package indexes now list the project as "ClickHouse Loom" by ClickHouse; install names (loom, clickhouse-loom, @clickhouse/loom, io.github.ClickHouse/loom) are unchanged. (#558)
  • The supported versions page states which releases get fixes (N and N−1), that an SDK at X.Y works with a server at X.Y or X.(Y−1), what the coding-agent plugins' minServerVersion means, how a removal is announced, and how to check a release image's signature. (#412)

Performance Improvement

  • The ingest worker takes the next job as soon as one ends instead of waiting 2 s after every job, so a queue of small documents drains at the speed of its jobs. LOOM_KNOWLEDGE_INGEST_INTERVAL_SECONDS now defaults to 0; a pass that finds no job still waits LOOM_KNOWLEDGE_INGEST_IDLE_INTERVAL_SECONDS. (#401)
  • The code index stays linear in a file's size on files with many same-named definitions, calls, classes, template literals, imports, deep directories, Go replace directives or tsconfig paths patterns; such a file took minutes to hours to index or to show in the PR overlay. (#482)

Backward Incompatible Change

  • A server whose LOOM_BIND_HOST is not a loopback address refuses to start with LOOM_MCP_AUTH_MODE=open or with a LOOM_JWT_SECRET below RFC 7518's minimum key size (an HMAC secret shorter than 32 bytes or an RSA key under 2048 bits; only the length is checked), or a gateway or inline end-user issuer key under 2048 bits, unless LOOM_ALLOW_INSECURE=1; loom doctor and /v1/health report both conditions. scripts/serve.sh, run from a checkout, treats a non-loopback LOOM_LISTEN_HOST as the bind address when the environment does not set LOOM_BIND_HOST (the image sets both). The development compose file and the per-worktree ClickHouse start script publish ClickHouse on 127.0.0.1 only. (#327)
  • With networkPolicy.enabled: false (the default) the Helm chart now renders an egress-only NetworkPolicy that keeps the Loom pods off the instance metadata service and allows every other egress. Pods that took cloud credentials from the metadata service, and releases whose own policies limit the pods' egress, set networkPolicy.guardMetadata: false. After a helm upgrade --reuse-values, set networkPolicy.egressExcept again to pick up the added IPv6 address. (#367)
  • Settings → Tokens has one form for every token: it reads everything you belong to (the default) or only the namespaces you tick, saves to the namespace you pick, and the result shows the setup for each MCP client or a script. POST /v1/me/setup/mcp_config is removed: POST /v1/me/service_accounts.create mints every token, takes namespaces: "*" (the default) or a list, and returns each MCP client's setup. The SDKs' token methods are renamed: in Python agent.account.service_accounts(), create_service_account(...), revoke_service_account(...) and mcp_config(...) become tokens(), create_token(namespaces="*", *, name=None, role=None, ttl_days=90, saves_to=None) and revoke_token(name); in TypeScript serviceAccounts, createServiceAccount and revokeServiceAccount become tokens, createToken and revokeToken, the ServiceAccount* types become Token*, and TokenRow drops kind and client. (#326)
  • Under either authority, knowledge.update_metadata with reindex=true now re-indexes also when the metadata is already current, so each such call re-embeds the document, and knowledge.bulk_add refuses a batch that names a source_uri twice (422 VALIDATION_FAILED, nothing written): send one entry per source. (#405)
  • A server on a non-loopback LOOM_BIND_HOST with LOOM_SELF_SIGNUP=open now refuses to start unless LOOM_IDENTITY_PROVIDER=trust-network or LOOM_ALLOW_INSECURE=1 is set or sign-in is OIDC (LOOM_OAUTH_LOGIN_PROVIDER=oidc, which closes sign-up), and loom doctor and /v1/health list open self-signup on a reachable bind. (#483)
  • A trusted caller (LOOM_TRUSTED_CALLERS) can no longer act with X-Loom-On-Behalf-Of for a token's own sa: subject (a dashboard, admin, agent sign-up or loom mint-token service account). Of the sa: subjects, it now acts only for an active bot and its agents, so the direct grants of a token under an sa: subject stop being reachable once the token ends. A token minted under a subject without sa: is acted for like a person's until that subject is revoked outright. (#531)
  • The default MCP listing (core) shows 13 tools instead of 34 (ten memory ops plus loom_search, loom_activate and loom_op), and the listed tools say when to use each one rather than a neighbour. On core, documents, working memory, profile, namespace and erasure ops run through loom_op; HTTP and the SDKs call them as before. A client on the default listing with no transcript hook no longer records conversation turns; it mounts ?toolset=all to keep them. Seven experimental ops that were listed on all only because core carried them leave all too (and memory.related, the one read among them, leaves readonly): conversation.create, memory.related, knowledge.delete, profile.set, profile.append, working_memory.replace and working_memory.delete. (#546)
  • A document source can be registered on any shared namespace or collection, not only a repository's trunk code namespace, the Library shows the sources of the place in view, and a source is retired when its namespace is deleted or erased. The source ops no longer answer NOT_A_CODE_NAMESPACE; knowledge.source.add, rotate and sync_now answer NAMESPACE_INACTIVE for an inactive namespace and PERSONAL_NAMESPACE for a person's own collection or namespace. knowledge.source.add answers 409 ERASURE_RUNNING while an erasure of the namespace runs, and namespace.restore brings a deleted namespace back without its sources: remove each with delete_documents=true and add it again. (#541)
  • An agent's claim code works once: a person enters it at /oauth/agent-claim with a password of their own, which replaces it, and no sign-in (dashboard, OAuth authorize, loom login) takes the code. This holds for agent accounts made before this release too: one already claimed by signing in with its code is claimed again at that page with the same code. The claim ends every token minted under the account before it, so mint those again; whoever holds the code claims first, so claim at once. (#336)
  • collection.create on a taken name answers 409 COLLECTION_NAME_TAKEN instead of PRECONDITION_FAILED; the SDKs still raise PreconditionFailed, and a client that matches the code matches the new one. (#471)
  • namespace.restore takes each field only as its column holds it: a snapshot payload with a value its column cannot hold is refused with 422 VALIDATION_FAILED before anything is written, where most such payloads failed with a 500. (#158)
  • On any deployment, a loom code command that reads its repository entry and whose org (--org, else LOOM_CODE_ORG) has no configured entry now refuses instead of indexing the first entry's checkout under that org. (#484)

Bug Fix

  • Most read-only knowledge ops (knowledge.get, list, ingest_status, formats, source.list, ...) draw from their own knowledge.read rate bucket (50 burst, 10 per second), so a page of reads or an agent polling an ingest no longer spends the budget knowledge.add needs, and is no longer refused after ten calls. (#291)
  • The Claude Code and Codex plugins hand the file rules recalled before an edit, the issue titles the code shunt quotes and the saved-brief list to the agent inside recalled-data blocks whose closing tag carries an id only the install can compute, so stored text cannot end them, and the conversation flusher and the code-shunt hook no longer follow or read a symlink, hard link or another user's file planted at their temp files. The code shunt's decision log moves to $TMPDIR/loom-shunt-log-<hash of the uid>.log, and a shunt denial whose budget cannot be recorded now lets the call through. (#321)
  • A JSON-RPC batch to POST /mcp holds 1 to 64 requests; a larger or empty batch is refused whole with Invalid Request before any entry runs. (#330)
  • The reference extractor reads a PDF font's ToUnicode map in time linear in its size, so a PDF whose map opens blocks it never closes no longer holds the extractor until its timeout. (#339)
  • The memory inspector's Last recalled now counts every agent read that recalled the memory (searches, context packs and the memory stack, memory.ask, procedure matches, the pre-edit file read), so a memory an agent was shown no longer reads "Never recalled". The ranking credit is shown beside it as Recall credit, and memory.get with include_recall_breakdown answers served_count and last_served_at. (#311)
  • A dashboard link carrying #token= signs the browser in without asking only when it answers the SSO sign-in this tab started, or when the token is for the account already signed in; any other link asks first, naming the account it signs in as. (#320)
  • A gdrive source no longer stores, chunks and embeds the base64 bytes of the pictures pasted into a document: a line of pictures becomes an [image-only: …] marker, as on a page store's page, and documents synced before this release are converted again once. (#312)
  • code.search (and the code grounding of memory.ask), tracker.ask and the decision extractor read identifiers in linear time, so a query holding a long run of capitals or digits no longer blocks them for seconds or minutes. memory.ask's own identifier patterns are fixed in #317. (#342)
  • Indexing a FlatBuffers schema with a long run of blanks inside a union member or a block header no longer stalls the code-index worker. (#357)
  • The Claude Code and Codex plugins' prompt-pack and code-shunt hooks no longer stall a prompt submit or a Bash call for seconds on a prompt with a long whitespace run after an opening tag, or a sed or awk program with a run of backslashes. (#365)
  • The C/C++ compiler tier fetches submodule pins with the repository entry's GitHub token, so a repository with a private submodule of its own owner no longer stalls its compiler tier at the first pin the checkout lacks. (#341)
  • A knowledge source's sync no longer overwrites a document a person writes at the same address while the sync is fetching and converting that file. (#353)
  • An entity name longer than 256 characters once normalised (NFKC turns a fi ligature into fi) is treated like one longer than 256 as written: it is no entity, and a point write's fact subject or object of that kind is refused with a message that says so. (#366)
  • Erasing a user no longer un-cancels another writer's document ingest that the user had cancelled: the cancel is kept without the user's identity. (#362)
  • A synced knowledge document full of unclosed image or link markup no longer holds the server for seconds while decisions are extracted from it. (#370)
  • Under Codex, the loom-session and loom-status skills use the installed loom plugin's own copy even when another marketplace holds an older copy of loom, and loom doctor --client warns when only another marketplace's copy is installed. (#372)
  • The reference extractor guesses a CSV's delimiter in bounded time: a CSV whose first lines are full of quotes and delimiters is sniffed in well under a second instead of seconds before any row is read. (#343)
  • An audited operation that declares no audit subject now records its text inputs as a SHA-256 and length, so loom.audit_log no longer keeps a decision's reasoning, a moved memory's value, an imported document or a restored snapshot after they are erased. Rows written before this release keep their text. Text fields in audit subjects written from this release, on the default path (a memory.delete reason, a decision.record scenario, ...) and in a cross-promotion's or copy's rationale, a proposal's reason and a supersession's closed, are {sha256, chars} objects where they were strings, so a reader of audit.list or loom.audit_log handles both shapes; a supersession row's actor is the writing principal, and a policy decision's row carries policy in place of reason. (#334)
  • The Claude Code and Codex plugins now work on Windows (x86_64 and arm64, with or without Git Bash) and on macOS checkouts reached through a symlink; a CI workflow runs the plugin suite and a real Claude Code and Codex session on Linux, Alpine, macOS and Windows. (#313)
  • Erasure deletes a user's or a namespace's traces on Langfuse 4 as well as Langfuse 3. On Langfuse 4 it finds them through the observations list, because Langfuse 4 no longer serves the trace list. (#348)
  • memory.reject refuses a locked memory with 423 LOCKED and writes nothing, as memory.delete does; a negative-outcome quarantine from memory.outcome leaves a locked memory as it is (its negatives keep counting). (#354)
  • A repository's tracker links are read per org: a link set with code.repo.set_source applies only to the repository in the org that set it, never to another org's code namespace of the same name. LOOM_TRACKER_CODE_NAMESPACE_BINDINGS_JSON links only the operator's own repositories (LOOM_CODE_REPOS_JSON or LOOM_CODE_REPO); a self-service repository is linked by its maintainer. A branch added with code.repo.add_branch from an operator repository reads that repository's links. (#331)
  • A commit: or error: name is one graph node everywhere: entities, relations, code facets, co-occurrence edges, query triples and the seeds of memory.search, facts.traverse and facts.path spell it the same way, so a search for error:Timeouts reaches what a write stored for it. facts.list and facts.related match names as the writers store them, and facts.related returns the name canonicalised like a write. (#383)
  • prompts.evaluate finds a prompt version's traces and scores on Langfuse 3 and Langfuse 4. Before, it found no traces on either and always answered should_promote: false. A failed Langfuse lookup or score read now answers 502 BACKEND_UNAVAILABLE instead of a "no traces" or "no scores" verdict, and traces sent before this release carry no prompt tag, so an evaluation counts only traces sent after the upgrade. (#389)
  • A document download or upload, an inbox watch, a claim release, a token mint and the MCP primer's profile read that are admitted to a restricted namespace now write the namespace.restricted_access audit row, as the op gate does for the namespace a request names. (#358)
  • A user erasure now deletes the cached memory.ask answers and the precedents that quoted a consolidated summary of the user's memories stored under another identity, with or without purge. A purge=true user erasure now also removes what the user's memories fed through their keys; before, it removed the memory rows first and then found none of it. (#360)
  • The C/C++ compiler tier re-runs the CMake configure when a merge adds a translation unit or edits the build, and before every full index, so a .cpp file added after an entry's first index gets compiler-grade symbols instead of staying on the Tree-sitter tier. (#379)
  • A read_only-locked memory keeps its live expiry, or none, through a memory.set that restates it: its memory type's retention no longer stamps a new expiry on a locked row, because the lock refuses an expiry change. (#381)
  • A cached code.ask answer that quoted memories of the bound memory namespace (LOOM_CODE_ASK_MEMORY_NAMESPACE) now records that namespace and the quoted keys, and an erasure of the namespace or of a user whose keys it quoted deletes it, as memory.redact does for a quoted key. The residue census counts these answers, so an erasure that leaves one fails (except under a user purge, where the census cannot read the user's keys). code.ask reads only the bound namespace, whatever the default memory.search scope. (#332)
  • A memory.apply row whose write fails unexpectedly reports the kind of error and the request id instead of the error's text, and a typed Loom error keeps its code and message. The history policy gate's errors in a 403, blacklist.add's sweep_error, blacklist.sweep's error, a failed loom_search section's error, an extraction batch's last_error, an erasure's failure_reason, code.index_status and ops.health worker errors, ops.health probe details and /v1/health errors name the error kind, not the store's text, including rows written before the upgrade. (#329)
  • The MCP session-start instructions now put the caller's profile after Loom's own rules, so on the coding toolset a client that cuts long instructions (Claude Code at 2,048 characters) keeps every rule however long the profile is. (#409)
  • The Claude Code plugin's X-Loom-Code-Scope header names the checkout the session works in. Before, it named no checkout for a plugin installed from a git marketplace, and the plugin's own checkout for one installed from a local clone. (#415)
  • A tracker source whose github_prs has no test_types or src_components column now meets the record contract, with both served as [] and reported as defaulted, instead of pausing every tracker read for its namespaces. (#416)
  • When the expiry sweep writes a memory's post-action but fails to update its search vectors, it records the miss as a projection failure, and the projection repair applies it later. Before this change, a demoted memory could stay hidden from search, and an anonymized one could keep its entity names in the vector tables. (#414)
  • /v1/health?include=mirrors and the /v1/health/ready mirrors check count the host's git mirrors instead of listing each entry's org/namespace label. A model call the inference gateway refuses or defers, by an answer LOOM_INFERENCE_IDENTITY_REFUSALS_JSON lists, now has a message with only the gateway's status and code. (#420)
  • A new setting, code_repos_shared_gid, names the group that shares the git mirrors and compiler-tier checkouts under var/code_repos between the host operator and the containers (uid 999): what Loom creates there is then writable by that group and closed to other users, so the host's loom code jobs and the containers can fetch into and check out what the other created. Unset (the default), mirrors and checkouts are 0700. (#417)
  • A first write into an unregistered namespace that already holds data or a live grant now registers it with no owner, so the writer cannot complete its registration; an admin does. Self-signup and agent sign-up now also refuse a name that a task inbox names as its owner or as a poster, that a poster rule names, or that posted a task. task.list posted_by_me lists only the tasks whose poster subject is the caller's, never tasks that merely carry its name as their agent label. (#421)
  • The code index reads a checkout's package.json and yarn.lock, and each compiler tier's output delta, only as regular files under a size cap, never through a symlink; a delta is capped at LOOM_CODE_SCIP_DELTA_FILE_MAX_MB (180 MiB) and LOOM_CODE_SCIP_DELTA_MAX_NODES (6,000,000 {/[/, bytes), both checked before it is parsed. A refused file fails that pass, as an unreadable one does. The ingest checks a mirror file's size before reading it, so a file over LOOM_CODE_INGEST_MAX_FILE_BYTES is recorded unread, and the TypeScript resolver's manifest and barrel parsing takes time linear in the file. (#322)
  • code.repo.set_access, the code namespace registrar and the boot-time tracker namespace registrar now hold the namespace's account claim while they read and rewrite its directory row, as namespace.register and namespace.delete already do. A namespace change that lands at the same moment as one of them can no longer be undone by a stale copy of the row. (#425)
  • loom reembed reads each page's rows again just before it writes their new vectors, and skips any row deleted, tombstoned, or given other text or another agent or user after the page was read, instead of writing back the text it read. When such an old-model row is still stored, --delete-old leaves that org's old-model rows for a later run. (#426)
  • The code_graph_stats worker pairs test files with source files in a thread instead of on the event loop. On a large repository the pairing held the loop for longer than the supervisor lease allows, so the deployment stopped every worker about every 20 seconds and no background pass finished. (#427)
  • The image and scripts/install-code-toolchains.sh install pnpm 12.15.0 instead of 12.3.1. The TypeScript compiler tier can now install the dependencies of a pnpm workspace whose pnpm-workspace.yaml sets lockfile options, which pnpm 12.3.1 refused to load. (#431)
  • A compiler tier no longer records a failure when only the git gc it runs on an entry's checkout fails; the failure is logged. Before, such a record stayed failing after its cause was fixed, until the tier's next run that was due, up to a week for the full C++ run. (#434)
  • A tree-sitter parse of a repository file stops reading it after LOOM_CODE_PARSE_TIMEOUT_S (3 s of CPU time by default); the file then takes the reader's parse-failure path, which for a file of a known language is chunks without symbols with the parse error recorded (the shell detector reads it as not shell). The PR overlay that code.callers, code.impact and review briefs read parses a PR's files in a thread, one at a time per namespace and once per file per namespace for concurrent requests, starts no parse after LOOM_CODE_PR_OVERLAY_PARSE_WINDOW_S (10 s), and counts the files it did not fully read in overlay_note. (#422)
  • Reading a parsed file's text no longer hangs once the reading thread has used more CPU time than the parse deadline allows. Such a read (the Python SQL-island extractor during a re-extract, for one) spun forever and stopped every background worker on its event loop. (#437)
  • A user erasure now also removes what a consolidated summary of the user's memories fed: the summary's version history (read by memory.history), its verbatim chunks and entity vectors, the graph rows extracted from it, and the org-wide copies promoted from it, with or without purge. A user purge now also removes the search rows of an org-wide copy promoted from the user's own memory, which it used to leave. (#423)
  • insights.query now works on ClickHouse Cloud, where it stayed unavailable with Setting log_queries should not be changed. The analyst's scope key and password stay out of system.query_log there too, including the error text of a read a row policy refuses. (#428)
  • insights.query now works on a ClickHouse that takes custom settings under a prefix other than SQL_, ClickHouse Cloud's custom_ among them. Loom finds the server's prefix when it loads the analysis secrets. (#442)
  • An operator entry in LOOM_CODE_REPOS_JSON whose namespace was added from the Repositories page is now skipped, with a warning in the log, instead of taking over that member's access settings and admin grants. A namespace a member created with namespace.register is still taken over when the operator lists it; review its grants first. An access row set on an operator entry is no longer listed once the entry leaves the configuration, instead of being listed as a page-added branch indexed with the operator's token. Both hold for registry rows this release writes; a row an earlier build writes, before or during the rollout, records no kind, and the #424 operator step covers it. (#424)
  • Every tree-sitter parse of a repository file or of a PR's posted files first runs in a child process held to LOOM_CODE_PARSE_MEMORY_MB (256 MB of address space by default) and LOOM_CODE_PARSE_TIMEOUT_S of CPU time; a file past either limit is indexed as chunks without symbols, with the parse error recorded, and the server process never makes that parse. (#435)
  • In an append_only namespace the conflict check of a memory.bulk_set of two or more entries now gives every entry its own row, as it does a single write; it could merge an entry away or mark it duplicate or conflicted. (Entries with identical text are still merged by the bulk write's identity dedup, a check of its own.) New LOOM_MEMORY_BULK_PEER_READ=per_fact lets a bulk write read each fact's conflict peers with memory.set's codes-first read, so a bulk write into a namespace of hundreds of thousands of memories does not run ClickHouse out of memory; the default, batched, keeps today's read. (#451)
  • An op whose ClickHouse statement runs out of the server's or the ClickHouse user's memory now answers 503 OVERLOADED with retry_after_s 5, as insights.query already did, instead of 500 INTERNAL_ERROR. A statement that hits its own memory cap answers as before. (#458)
  • The code index reads Markdown, Python layout manifests (setup.py, pyproject.toml, conftest.py sets), Dockerfile, Jenkinsfile, Angular, Vue, Svelte, Astro and dbt files, CMake command rows, and route and environment-variable facets, in time linear in the file's size. A crafted file, in an indexed repository or in the files a request posts for the PR overlay, could hold a serving replica or the indexer for minutes to hours. (#448)
  • The code index reads CMake files, Python manifests and entry points, Markdown references, the import candidate lists of Python, TypeScript, Rust, Go and Protobuf files, and the HTML comments of PR descriptions in time linear in their size. A crafted file in an indexed repository, a file posted for the PR overlay, or a PR description could hold a replica or the indexer for seconds to hours, or exhaust its memory. (#463)
  • Memory search's key-match arm works in a namespace that holds a memory key with whitespace in it (communication style). Before, every key-match read in that namespace failed and the arm added nothing. (#475)
  • A tracker source whose github_prs lacks affected_file_paths or diff no longer fails every read of a PR's files (pr_files, prs_touching_paths, pr_change and the bisect scans) with Unknown expression or function identifier. The contract check reports the columns as defaulted, and the reads answer as for a PR that touched no file. (#477)
  • The token form's Only these namespaces starts with nothing ticked, lists every namespace you belong to (your personal one included), and shows the ticked ones as one search order that you rearrange with its Move up and Move down buttons, or on a desktop by dragging the row's grip (a touch screen, where a long-press drag is not what a person expects, shows the arrows only). (#481)
  • The Loom assistant is told to write nothing before or between its tool calls, so on the dashboard's Assistant page a lead-in such as "Let me search the memories." no longer comes before the answer and runs into it, as long as the model follows its instructions. (#479)
  • A deployment-wide lease whose renewal call hangs (a ClickHouse read past its deadline) is now cut short and retried inside the window before the lease counts as lost, so one hung call no longer ends a long compiler-tier full run. (#489)
  • /v1/health reports workers_lease_absent only when no process holds a live workers lease, so a web container that runs no workers no longer shows it while a workers container running auto runs them. (#490)
  • On a single-user install, Settings → Tokens → Create token mints a token instead of failing with "cannot delegate access on local", and the token belongs to the install's org (dev) rather than default. (#501)
  • The documented install command now saves the installer before running it (curl -fsSL …/install.sh -o loom-install.sh && sh loom-install.sh && rm loom-install.sh), so a failed download stops with curl's error and a non-zero exit instead of exiting 0 with nothing installed. (#500)
  • The manual Python compiler-tier pass, scripts/scip_harness/run_python_delta.sh, runs again: it failed on every entry with RegistryUnread because it listed the configured entries before reading the self-service repository registry. (#530)
  • When a dashboard write is saved but lists and search have not caught up yet, the page now shows it as made: a pin stays pinned, an edit closes, a dialog closes, and a deleted document keeps its Undo. Before, the page showed it as failed and invited sending it again. A rename that the server answers this way before it has moved the old key says a second Rename finishes it. (#534)

Critical Bug Fix

  • code.search with a one-entry namespaces list authorizes that namespace before searching it, as it does each entry of a longer list; a caller who cannot read it gets 403. code.repo.set_source stores a link only to a tracker the caller administers, unless the repository's own links already hold it; a reader of the tracker gets 403, anyone else NOT_A_TRACKER_NAMESPACE. Keeping, reordering or dropping a link the repository's stored links already hold is unchanged; a tracker only the deployment's map links counts as new. inbox.register names the namespace that owns an inbox only to a caller who can read it. (#310)
  • Off the inference gateway, a namespace's classify_endpoint must be https with no user name or password, and memory.set(classify=true) posts only to the public address its host resolves to, with no redirect and no gateway identity headers; a failed call is reported in degraded by its kind alone. LOOM_MEMORY_CLASSIFY_PRIVATE_HOSTS names the hosts an operator allows on a private address, over http too. (#318)
  • Blacklist regexes run on RE2 on every write, as they already did in the sweep: a pattern RE2 cannot run (a backreference, lookaround, \Z), one that compiles to more than 300 RE2 instructions, or one that would take its scope's regexes past 3,000 together is refused at blacklist.add; the match runs on the blacklist's own threads, off the event loop, and a value too long to check against the regexes that apply (bytes times instructions past 250 million) is refused as BLACKLISTED with an empty pattern_id. Sensitivity detection of emails and JWTs runs in linear time on any value, and a write reads a value past 16 KiB in overlapping windows with a turn for the event loop between them. (#307)
  • A branch added from the Repositories page no longer makes its admins admins of the operator's configured entries for the same repository, and a revoke there no longer reaches them. The operator's admins still become admins of every branch added from the page. Copies already on an operator namespace stay until revoked: the code index documentation on adding repositories says how to find them. (#338)
  • Self-signup and agent sign-up refuse a name that is a LOOM_TRUSTED_CALLERS subject, a LOOM_PLATFORM_ADMINS handle, or a subject holding a grant (a pending join request included), a namespace or an unrevoked token in the org, and sign-up tokens carry writer only. A trusted caller's X-Loom-On-Behalf-Of naming a subject revoked or deprovisioned under any spelling it holds is refused with 403. (#305)
  • namespace.adopt refuses a restricted namespace and answers an unregistered name the same way, memory.sweep_expired requires a namespace, audit.list and the org-wide learnings.quality run the shared scope check (collections, restricted namespaces and delegated tokens included), and a tracker source is managed by an admin of its namespaces, not by whoever registered it. A private repository's code index stays unindexed and its mirror private until it is restricted, and a repository already indexed under another spelling is refused as a duplicate without naming the namespace that holds it. (#323)
  • The regexes that read request text on the event loop (dashboard asset names, memory.set file detection and its stack-trace hint, code.verify_citations, code.search, code.trace, tracker.similar and tracker.duplicate_check frames, memory.ask, memory.set_from_messages, knowledge.search, loom_search, SCIM group filters and others) run in time linear in their input. tracker.similar and tracker.duplicate_check refuse a frame, symbol or path over 4,000 characters, and GET /assets/ treats a file name over 255 characters as not a hashed asset and answers 404 for a path over 1,024 characters. (#317)
  • Self-signup, agent sign-up and every op that registers a namespace as new and makes its caller admin (namespace.register, namespace.restore into a fresh target, collection.create, self-service code repositories and tracker sources) refuse a name that already holds data or a live grant with no registry row. The ops still accept it from a caller that may already act on it as admin, and with auth off. Before, any of these could take such a namespace and make the caller its admin. (#340)
  • A restricted repository branch added from the page now takes admin grants only from the operator's entries of its repository. Before, any org member who added another branch of the same repository from the page was copied in as its admin. After upgrading, run code.refresh on each restricted branch added from the page, so its stray copies go at once, then review its grants: a member who held a copy there may have granted or approved others, themselves included. (#406)

Build/Testing/Packaging Improvement

  • The document extractor image ships pypdf 6.21.4, which fixes security bugs in reading malformed PDFs. (#275)
  • A document-parsing bench (python -m bench.docparse) scores the reference extractor on a labelled corpus of design docs, specs, runbooks and decks as PDF, DOCX and PPTX: text, reading order, headings, tables, page locators and silent loss. (#289)
  • The server image's Go toolchain (1.27.3) and Node runtime (22.23.4) carry no critical CVE that has a fixed release, and a weekly image scan reports one before a release meets it. (#319)
  • Knowledge ops have declared service levels (p95 and p99 for search, structured queries and ingest, and the time a small document takes to ingest), and bench/knowledge_load.py runs a mixed knowledge load against a server and checks it against them. (#394)
  • Both release images, ghcr.io/clickhouse/loom and ghcr.io/clickhouse/loom-extractor, carry a keyless cosign signature from the release workflow, so cosign verify with the release workflow's identity proves an image came from that release's tag. (#407)
  • bench/scale_load.py loads synthetic memories into ClickHouse and the Postgres authority and measures the memory ops, refusals, parts and merges, the vector leg, erasure, Postgres sizes and ClickHouse CPU per call; docs/benchmarks/results.md § "Memory at scale" reports where memory's ceilings are and what a million memories cost at list prices. (#454)
  • The server is built with dnspython 2.9.2. (#491)
  • The PyPI, npm and MCP Registry listings name Loom's shared long-term memory, document search and code index, with keywords such as agent-memory, document-search and code-search; the PyPI and npm pages link the SDK reference pages. (#506)

Operator steps

  • #291: a knowledge.add override no longer applies to the knowledge.read ops, at either tier. This holds whether it was set through admin/ratelimit.set or by dev/onboard_tenant.py, and whether it sits above or below the default. Those reads take the knowledge.read limits: 50 / 10 per namespace and 150 / 30 per org, unless an override exists on knowledge.read. To keep a tenant's reads at the limit its knowledge.add override gave them, set the same override on knowledge.read (POST /v1/admin/ratelimit.set with op_class: knowledge.read, namespace or org). Running dev/onboard_tenant.py again sets the namespace one, as it now raises knowledge.read. A tenant with no override needs nothing.
  • #314: none to upgrade. To take the .docx files of a folder an existing source already syncs, add a second source on the same folders with include: ["docx"]: it writes only the .docx files, whose addresses no source holds, and leaves the first source's documents alone (a sync never writes or retires a document another source holds). A deployment with no extractor for docx counts each such file unconverted (no_extractor), as for PDF.
  • #321: anything that reads $TMPDIR/loom-shunt.log reads $TMPDIR/loom-shunt-log-<hash of the uid>.log instead.
  • #293:
    • The label is written with the chunks. A document stored before this build, written through an N-1 replica during the rollout, or retitled by knowledge.update_metadata without reindex=true, keeps its chunks' earlier label (none reads as content) until they are written again. Run the relabel below once the last N-1 replica is gone.
    • Text documents. A text document, a synced one included, is relabelled by loom rechunk --org O --namespace N --apply; a sync rewrites only the files that changed. That command also rewrites every ingested file in the namespace into a text document, so in a namespace that holds ingested files, run it per text document with --source-uri.
    • Ingested files. An ingested file is relabelled by a new knowledge.ingest of its original.
  • #327: a public deployment running LOOM_MCP_AUTH_MODE=open switches to token auth, and one with a weak root key rotates to an HMAC secret of at least 32 bytes or an RSA key of at least 2048 bits, and replaces any LOOM_JWT_PUBLIC_KEYS_JSON or inline end-user issuer key under 2048 bits (or sets LOOM_ALLOW_INSECURE=1, only on a port others cannot reach). Someone who reached the dev ClickHouse from another machine now goes through an SSH tunnel.
  • #310: review existing repository links to trackers (code.repo.set_source state) made by maintainers who are not the tracker's admin (the qa_binding key in loom.code_index_state); links stored before stay in force until changed. During a rolling upgrade the review also covers qa_binding rows written until the last N-1 replica stops, since N-1's set_source has no admin check.
  • #318: before upgrading, a namespace whose classify_endpoint is http moves it to https: from its first request the new build no longer calls a stored http endpoint (writes go unclassified), unless its host is listed in LOOM_MEMORY_CLASSIFY_PRIVATE_HOSTS. Also before upgrading, make each entry of LOOM_MEMORY_WEBHOOK_PRIVATE_HOSTS and LOOM_INFERENCE_GATEWAY_HOSTS a bare hostname or IP literal: an entry with a port, brackets, a scheme, a path, a wildcard or a zone now stops the server at start. A deployment that runs its classifier beside Loom on a private address lists that host in LOOM_MEMORY_CLASSIFY_PRIVATE_HOSTS. A webhook receiver or tracker host that only /etc/hosts or a search domain names is either listed in its private-host setting or given its full DNS name; a git remote or submodule the egress proxy fetches is given its full DNS name.
  • #307: find stored regexes this build drops (SELECT pattern_id, pattern FROM loom.blacklist FINAL WHERE pattern_type = 'regex' AND tombstoned_at IS NULL, then loom logs blacklist: dropping regex pattern at cache load) and replace each with an RE2 form.
  • #312: none; loom knowledge sync --full converts the stored documents at once instead of at the next full listing.
  • #344: chunks written before this change keep their label until they are relabelled as the #293 step says.
  • #347: a deployment that runs a tier harness from cron under loom code entry-lock adds --tier <tier> to that call to have its failures recorded.
  • #348: on a deployment upgraded to Langfuse 4 from a Langfuse 3 release earlier than 3.227.0, run Langfuse's historic backfill before relying on erasure; the Langfuse 4 lookup finds only traces in the observations list. An in-place upgrade from 3.227.0 to 4.76.0 was tested and needs no step. The erasures to request again are in the erasures item below.
  • #158: a new deployment that wants the Postgres memory authority starts on an empty ClickHouse and follows Postgres; an existing deployment leaves LOOM_STORAGE_AUTHORITY unset (it refuses to start with postgres once its ClickHouse holds memory) and needs nothing (its migration applies at boot).
  • #331: a self-service repository that relied on a glob in LOOM_TRACKER_CODE_NAMESPACE_BINDINGS_JSON needs a link from its maintainer (code.repo.set_source or the repository card's Trackers section). A branch of a self-service repository gets that repository's maintainer link when it is added; a link set on the repository later is set on the branch by an admin of the tracker with code.repo.set_source. Every self-service branch of an operator repository added before this change has no qa_branch_of row and loses its glob link at upgrade. To find them: the registry entries (code.repos, source registry) in the same org and repository as an operator entry. To restore one, an admin of the tracker links it with code.repo.set_source, or the operator lists it in LOOM_CODE_REPOS_JSON; a repository maintainer who is not the tracker's admin is refused, as for any new link.
  • #367: a release whose pods take credentials from the metadata service, or whose own policies limit the pods' egress, sets networkPolicy.guardMetadata: false (the first can also move to IRSA or EKS Pod Identity). A release upgraded with --reuse-values keeps its old egressExcept, without fd20:ce::254/128, until it is set again.
  • #392: none for the shipped dashboards. Saved searches that filter on the old keys need the new ones; rows written before the change keep the old keys. In Langfuse, embedding calls are now EMBEDDING observations, so a filter or saved view on type GENERATION no longer includes them or their cost.
  • #338: this stops new mirrors from page-added branches and does not remove rows written before it. Build N-1 replicas keep mirroring both ways during a rolling deploy, so run the review below after every replica runs this build. To review existing ones: list the grants on operator code namespaces whose granted_by is repo-maintainers:<namespace> with a page-added source namespace, and the subjects who hold them. Then list what those subjects did on operator namespaces while they held admin: grants whose granted_by is one of them (readers they approved and admins they made; an admin they made keeps being copied across operator entries until revoked), rows of loom.code_repos whose created_by is one of them: a page-added branch they added, or an access row for an operator namespace written by code.repo.set_access (a widened classification or join policy, which wins over the configured one; undo it by setting access again with the operator's values), and trackers they linked. Revoke or undo what should not stand. Also list the grants.revoke and lowering audit rows on page-added namespaces whose subject was then an admin of an operator entry of the same repository: the fan-out of such a revoke took the subject off the operator entries too, with no audit row there, so restore the operator grants it removed.
  • #390: a deployment that sets LOOM_CLICKSTACK_USER_ID_KEYS explicitly keeps its own list, and adds loom.user_id and langfuse.user.id to it.
  • #379: none. An entry's next full index picks up every translation unit added since its compile database was written. To get there sooner, run loom code scip-full for the entry. A CI job that uploads C/C++ deltas and copies in a compile_commands.json without its CMakeCache.txt, or with one made in another directory or naming a compiler the runner lacks, now configures from scratch, and every upload run invokes CMake before a full index and on a window that adds a translation unit or edits the build, so its runner needs CMake and Ninja, and clang for a configure from scratch.
  • #398: none; to page on a failing tier, enable the chart's rule or the dashboard's alert.
  • #332: a deployment that has set LOOM_CODE_ASK_MEMORY_NAMESPACE, now or at any time before, clears the answer cache once the newer build serves alone, with no provider rebuild running: TRUNCATE TABLE <LOOM_CH_DATABASE>.code_answers (loom.code_answers by default; upgrade procedure, step 9). A provider rebuild publishes its copy of the table by RENAME, so one running across the truncate would bring the rows back. Each cleared answer costs one re-synthesis.
  • #401: none. A deployment that relied on the pause sets LOOM_KNOWLEDGE_INGEST_INTERVAL_SECONDS=2.
  • #305: review existing accounts whose name equals a trusted-caller subject or a platform-admin handle and that the intended person did not create. loom claim-release removes the account but not its tokens: revoke the subject's tokens issued up to now (loom revoke-token --subject <name> --issued-before <now>), then re-mint the real trusted caller's token, or move the trusted caller to a new subject. If a squatter already holds a platform-admin handle, remove it from LOOM_PLATFORM_ADMINS first (a released name stays reserved, so the person cannot take that handle back), then list a new handle once the real account exists. Sign-up now refuses a listed handle, so a new platform admin signs up first and is listed after (deployment).
  • #397:
    • Where Loom's ClickHouse user has no access management, an admin runs the SQL loom ch-setup --print-analyst-sql prints. Until then insights.query answers ANALYSIS_UNAVAILABLE and doctor says so.
    • A self-managed ClickHouse may add access_control.xml to close the dropped-policy window; without it, the window lasts until the next probe.
  • #406: after upgrading, once the last older replica has stopped, call code.refresh on each restricted branch added from the page, so its stray admin copies go at once, then review its grants and approvals: a member who held a copy there may have granted or approved others, themselves included.
  • #418: none unless enabling the assistant. To enable it with Compose, add LIBRECHAT_CREDS_KEY, LIBRECHAT_CREDS_IV, LIBRECHAT_JWT_SECRET, LIBRECHAT_JWT_REFRESH_SECRET, LIBRECHAT_ANTHROPIC_API_KEY and LOOM_ASSISTANT_LIBRECHAT_URL=http://librechat:3080 to .env, then start the assistant profile beside the ones the stack runs: an installed stack adds it to COMPOSE_PROFILES and runs docker compose up -d; a checkout adds --profile assistant beside make dev's --profile bundled (the guide has both commands). LibreChat's model key has a name of its own because the loom service loads the same .env. On Kubernetes, set assistant.enabled and assistant.existingSecret to a Secret holding CREDS_KEY, CREDS_IV, JWT_SECRET, JWT_REFRESH_SECRET and ANTHROPIC_API_KEY (no LIBRECHAT_ prefix); the chart sets LOOM_ASSISTANT_LIBRECHAT_URL itself.
  • #420: none. A probe that parsed mirror labels out of health should count instead, or run loom doctor on the host.
  • #417: a host that shares var/code_repos with the containers (the 1.1.0 Writable git mirrors setup) sets the gid when it upgrades: check getent group 999 holds only the host operator and the containers' user (move the containers to a dedicated gid otherwise), set LOOM_CODE_REPOS_SHARED_GID in .env to that gid (999 when the check passes), and run, with that gid, the chgrp/chmod in Writable git mirrors again for files an older build created. Once set, restricted mirrors become readable by that group on the first round. The step that set core.sharedRepository per clone is gone. A deployment that runs loom code only inside the containers needs nothing.
  • #421: an auto-registered row written by an earlier build keeps the owner it named, even on a name that already held another identity's data. Review rows tagged auto-registered and not adopted whose owner_email is set on namespaces holding other writers' rows, and re-register any that should not be that owner's as an admin of the name.
  • #322: an operator whose real delta passes 180 MiB or 6,000,000 counted bytes raises LOOM_CODE_SCIP_DELTA_FILE_MAX_MB and LOOM_CODE_SCIP_DELTA_MAX_NODES; they apply on the next pass, without a restart. The lanes outside the extractor stamp (SQL, HCL, proto, Jsonnet, Go templates, Kubernetes, TLA+, bats and the other lanes) keep their current rows for a file until it next changes; loom code backfill --paths-file re-reads chosen files now.
  • #419: none beyond #418's. A deployment whose people should use the assistant needs LOOM_AUTH_REQUIRED: an answer is delegated from its caller's token.
  • #431: a native installation reruns sh scripts/install-code-toolchains.sh.
  • #424: a code registry row an earlier build writes, before or during the rollout, records no kind. For an operator entry's namespace it is read as the entry's access row, so a namespace a member added from the page (code.repo.add or code.repo.add_branch) stays under the entry, with the operator's token and the member's access settings; once the entry leaves the configuration, the row is listed as a branch added from the page. Once the last older replica has stopped, and before removing or moving an operator entry, review each such namespace and the grants copied from it (granted_by = repo-maintainers:<namespace>), as adding repositories says. Before listing a new operator entry, check that its namespace has no directory row yet (namespace.get); one a member registered keeps that member's grants.
  • #435: on an installation outside the image, the tree-sitter grammars must be installed in the server interpreter's own site-packages, as the image does: the child ignores PYTHONPATH and the user site, and the server refuses every parse with a LookupError (each file then indexed as chunks without symbols) when it loaded tree-sitter or a grammar from elsewhere. The server image's Python starts the child (sys.executable -I), so it needs no other install.
  • #451: none; a deployment that bulk-writes into large namespaces may set LOOM_MEMORY_BULK_PEER_READ=per_fact (performance tuning).
  • #450: none to upgrade. To use the surface, run loom sql-access grant as a ClickHouse user with access management.
  • #467: none for an existing release. A Postgres deployment sets postgres.* at install, on an empty ClickHouse.
  • #456: under LOOM_STORAGE_AUTHORITY=postgres, the Loom role must be able to run pg_logical_emit_message (its text overload; Postgres 17 added a fourth argument with a default). It is executable by PUBLIC on Postgres 16, 17 and 18 unless revoked, and boot refuses a role that may not, naming the grant.
  • #460: under the Postgres authority, after restoring one store, follow the restore runbook in backup and recovery.
  • #483: a deployment on a non-loopback bind with LOOM_SELF_SIGNUP=open, LOOM_IDENTITY_PROVIDER empty or gateway, and sign-in that is not OIDC stops starting. Set LOOM_IDENTITY_PROVIDER=trust-network if everyone who can reach the port may have an account, otherwise set LOOM_SELF_SIGNUP=off.
  • #478: none. A deployment that raised LOOM_CODE_SCIP_DELTA_FILE_MAX_MB and LOOM_CODE_SCIP_DELTA_MAX_NODES for memory reasons can now raise them for size alone.
  • #490: a workers container that runs LOOM_BACKGROUND_WORKERS=on writes no lease row, so beside it the web replicas now report workers_lease_absent at warn. Switch it to auto with LOOM_DEPLOYMENT=1, as the compose files and the Helm chart already run it.
  • #531: a trusted caller that acted for a service account's subject must act for the person or bot instead, or call with that service account's own token.
  • #497: none until a router is deployed (loom router migrate, then loom router serve). A cell needs the Postgres authority and authentication on every transport, so a deployment upgraded from 1.1.0 does not become one; cells has the steps.
  • #541: once the newer build serves alone, a roll forward after a rollback included, follow upgrade procedure step 13. It lists, with their queries, the active sources on namespaces an older build deleted and the sources registered before a namespace erasure completed that the erasure did not retire, whether an older build ran that erasure or added the source while the newer build's erasure ran, and that were still active when it completed (active now, or retired after it). A namespace erasure an older build started and the newer one finished needs no step: its final census reopens the steps for what the source wrote back. For the first list, remove each source, or delete its namespace again with the newer build; for the second, remove each source with delete_documents=true (which accepts an already retired source) and delete with memory.delete the decision rows mined from its documents, or erase the namespace again only where nobody has written to it since. The second list is a worklist: a removed source stays listed.
  • #348, #360, #390, #423, erasures: an erasure an older build ran can have left what this release's saga reaches. A user erasure left what a consolidated summary of the user's memories fed (its history, chunks, vectors, graph rows and shared copies) and the cached memory.ask answers and precedents that quoted it. With LOOM_CLICKSTACK_DATABASE set, it left the user's model-call spans, which carry loom.user_id or langfuse.user.id. Against Langfuse 4, a user or namespace erasure that ran the external backends failed at langfuse.delete_user or langfuse.delete_namespace (its failure_reason), so its clickstack.* step and the final verify.residue never ran. Once the newer build serves alone, and after adding those two keys to an explicit LOOM_CLICKSTACK_USER_ID_KEYS list, request again, from what loom.erasure_requests still lists (it keeps 90 days), the user erasures, and the namespace erasures that failed at langfuse.delete_namespace, each with the purge it had. A user erasure requested again also erases what the user wrote since, so request one only for a user with no live memory row, or where that is wanted; a namespace erasure requested again erases everything the namespace holds now, so request one only where that is wanted. What an older build's purges left, and the query for the shared copies to review, are in upgrade procedure step 10.
  • #292, #295, #298, #301, #302, #308: a document extracted before this release keeps its artifact until it is ingested or synced again; a writer re-approves the dataset of a workbook whose header this release renames.
  • Settings: every setting this release adds has a default, and none needs setting to upgrade except LOOM_CODE_REPOS_SHARED_GID on a host that shares the mirrors (#417), LOOM_MEMORY_CLASSIFY_PRIVATE_HOSTS for a classifier on a private address (#318), and LOOM_CODE_SCIP_DELTA_FILE_MAX_MB and LOOM_CODE_SCIP_DELTA_MAX_NODES where a compiler-tier delta passes their defaults (#322); the configuration reference lists them.
  • #336: an agent account claimed before this release by signing in with its code no longer signs in with it: its owner claims it again at /oauth/agent-claim with the same code, at once, and mints again the tokens minted under it before the claim.
  • #424, #484: a scheduled loom code run whose org has no configured repository entry now exits non-zero: pass the entry's --org, or give that org an entry of its own. loom code scip-lock-path now requires --namespace, and a code call or loom code command that names no namespace answers DEFAULT_NAMESPACE_REFUSED when a member added LOOM_CODE_NAMESPACE from the page: pass --namespace.
  • #546: a deployment that records conversation turns from MCP clients with no transcript hook sets LOOM_MCP_TOOLSET=all, or each such client mounts ?toolset=all.
  • Plugins: the Claude Code and Codex plugins are at 1.2.19; users update them from the marketplace.

What changed for clients since v1.1.0

361 operations served (was 353); ops +8 −0; request fields +3 −0; response fields +19 −0.

New operations

  • assistant.ask — role VIEWER, budget 30000 ms, MCP extended; request fields: history, namespace, org, question
  • help.get — role VIEWER, budget 20 ms, MCP extended; request fields: anchor, offset, org, path, section
  • help.search — role VIEWER, budget 50 ms, MCP extended; request fields: limit, org, query
  • insights.catalog — role VIEWER, budget 20 ms, MCP extended; request fields: namespace, org
  • insights.query — role VIEWER, budget 2000 ms, MCP extended; request fields: limit, namespace, org, sql
  • knowledge.formats — role VIEWER, budget 5500 ms, MCP extended; request fields: namespace, org
  • knowledge.query — role VIEWER, budget 1500 ms, MCP extended; request fields: aggregates, datasets, group_by, joins, limit, namespace, order_by, org, select, where
  • knowledge.restore — role WRITER, budget 200 ms, MCP extended; request fields: if_revision, namespace, org, source_uri

Changed operations

  • code.index_status: response +tier_health
  • collection.create: request +derive_name, title; response +title
  • conversation.append: mcp_tier core → extended
  • conversation.create: mcp_tier core → extended
  • erasure.export: mcp_tier core → extended
  • erasure.request: response +census, purge; mcp_tier core → extended
  • erasure.status: response +census, purge
  • knowledge.add: mcp_tier core → extended
  • knowledge.delete: mcp_tier core → extended
  • knowledge.get: mcp_tier core → extended
  • knowledge.list: mcp_tier core → extended
  • knowledge.search: response +stale_dropped
  • knowledge.source.list: response +accepts_sources
  • knowledge.synthesize: request +datasets; response +calculations, clarification
  • memory.get: response +last_served_at, served_count
  • memory.import_evidence: response +unchecked
  • memory.list: mcp_tier core → extended
  • memory.related: mcp_tier core → extended
  • memory.restore: response +redacted
  • memory.search: response +stale_dropped
  • memory.search_trace: response +stale_dropped
  • memory.set_from_messages: mcp_tier core → extended
  • memory.sweep_expired: response +moved_skipped
  • memory.update_by_id: mcp_tier core → extended
  • namespace.delete: response +sources_retired
  • namespace.list_mine: mcp_tier core → extended
  • namespace.restore: response +publication_pending
  • namespace.summary: mcp_tier core → extended
  • namespace.validate: mcp_tier core → extended
  • profile.append: mcp_tier core → extended
  • profile.set: mcp_tier core → extended
  • working_memory.append: mcp_tier core → extended
  • working_memory.delete: mcp_tier core → extended
  • working_memory.replace: mcp_tier core → extended
  • working_memory.set: mcp_tier core → extended

Removed routes

  • POST /v1/me/setup/mcp_config: POST /v1/me/service_accounts.create mints every token and returns each MCP client's setup (#326).

New Contributors

Full Changelog: v1.1.0...v1.2.0