Repository navigation
PureScience v2.19.0
Maturity and Known Limitations
We label every capability the way we build it: ✅ shipped and verified on a real machine; 🚧 partly done, with the gap written next to it; 🗺️ planned. Each claim is traceable — a commit, a real-run log, or a record under docs/evidence/.
-
✅ Shipped and machine-verified: A windowless run used to stall where nobody could look, and the review that was supposed to audit it never started.
start --no-openhas no window to click a permission card in: a real task sat after its first gated tool call with no further event for six minutes.GET /api/v1/permissionsandPOST /api/v1/permissions/:id/approve(purescience permissions/purescience approve) now read and answer those cards through the same narrow Task view (acp:get-state/acp:respond-permission, 11 → 13 commands — without them the route exists and answers 500), and approving is a named authority,satisfy-approval, which a generic task context cannot exercise: "who approved this" is never abstracted into "automation". Auto-review moved from the renderer to main, because a window was its only trigger — two identical CLI runs, the one with a window open reviewed, the other recorded nothing. Both paths converge through the existing already-reviewed check, so a turn is reviewed once. That first version of the trigger read the session once and so never fired at all — theend_turnevent lands before the session is written (~1.8 s later, measured), which left a finished CLI turn unaudited with a clean-looking log; it now waits out the write on a bounded schedule and reportsunreadablerather than dropping a turn silently (v2.15.3, measured before/after on the same CLI channel: 0 review rows → 1, verdictpass). Also in this version: a review persists outside any default five-second transaction budget, names its failures in seven families (the raw text kept as evidence), and stops at 300 tool calls per session; a client reset on a tunnel is no longer anuncaughtException; artifact finalisation waits out a durability race with a bounded retry instead of failing the turn; a Session leftrunningby a dead process is closed as interrupted at startup, and a Session that died between "plan approved" and "no longer waiting" comes back idle instead of showing an approval that already happened; a notebook kernel now leads its own process group on POSIX, so teardown is onekill(-pgid)instead of a pid walk that misses whatever forked mid-teardown and cannot see a child launchd adopted (the old path reportedreaped: trueon that), with the group claim checked against the kernel's real pgid and a named log when it does not match; external-source verification no longer fails every call (an emptyresponse.urlfell through??intoInvalid URL:, and a redirect's three outcomes now have three answers); and the review locator no longer demands a hash the model had to guess —contentHashis optional, pinned to the frozen block, withexpected/receivednamed on a mismatch. Measured during the audit that produced this batch, on the 2.15.1 candidate build: a windowless review returningflaggedacross five checks, 26 real approvals in the same run (PubMed / DOI / Wayback), three livefetch_sourcefetches (6,988 / 7,018 / 5,403 characters), and that runcompletedwith its artifact written. Re-checked on the build of this commit: the app boots headless in 3 s andGET /api/v1/permissionsanswers200 {"data":[]}with no window open — the failure that replaced was a 500 from that same route. The performance conclusion was wrong and the correction is the reading: the 118.9 s save peak and the 203 s projection panel were one process-level freeze, caught by an event-loop stall watchdog that samples from a second thread while the loop is stuck — withdrawn, not optimised around. Not done here: the read-only expression for built-in specialists. -
✅ Shipped and machine-verified: Batch removal is one write now: all of the ticked rows go, or none do. The previous version looped the single removal and said what that cost — a crash halfway could leave half the rows gone, and copy pruning decided row by row would depend on list order. This writes the register once and judges pruning against the sources that remain, so two sources sharing bytes clean up once and a surviving source never loses its copy. The change also changes what a refusal means, and the copy had to follow: an atomic batch that fails was not partly applied, so the sentence says the batch was not applied and nothing was removed — and the old "removed 3, 1 refused" string is deleted from all nine dictionaries, because a key describing a failure that can no longer happen is worse than no key. The armed-then-confirm step and page-scoped select-all stay: they guard against a human misclick, which has nothing to do with whether the batch is a loop. Measured: three registered, two ticked, armed, confirmed —
1 passed (7.4s), the unticked row still there. Sources can be removed in batches, without a new channel. One batch is N runs of the single removal, so copy pruning by reference and the verbatim refusal of a rejected row keep working exactly as they always did — a batch cannot become a shortcut around them. Three restraints: the first click only arms the action and a second confirms it, because this is the one control whose effect cannot be undone from the screen; select-all covers only the current page, since ticking rows nobody has looked at would decide on their behalf; and a row the store refuses stays where it is, with the reason shown verbatim. Measured: three registered, two ticked, armed and confirmed —1 passed (5.4s), and the unticked source is still there. The trade this version made and names: a batch is not atomic, because it is N writes, so a crash halfway could leave some rows gone and some not. An atomic alternative (one write, pruning judged against what remains) was built and not adopted; the choice is recorded in the evidence file. Not done: no undo, paging is still client-side. Molecular dynamics on CPU, and an honest answer about reproducibility. GROMACS 2025.4 from conda-forge minimises crambin and runs 10 ps in seconds: 11 frames, 642 atoms, 303 K measured against a 300 K target, mean potential −2859.6 kJ/mol. The important finding is a negative one — the output is not byte-reproducible, measured at 4 threads, at-nt 1and fully serial. So the contract differs from inverse folding: the input fingerprint (structure sha256, steps, temperature, seed, force field, engine version) is recomputable anywhere, frames and statistics are what two runs should agree on, and the trajectory digest is the identity of this artifact rather than a promise it can be regenerated. The panel says that next to the digests and the engine payload carriesbyteIdenticalOutput: false. Not done: vacuum rather than solvent, no trajectory viewer, no RMSD analysis, and binder design is still blocked on a CUDA host. A sweep now leaves a reading behind. Every completed sweep records when it ran and what it found — scanned, kept, removed, still-failed, bytes freed — and that record rides along with the source list the panel already fetches, so no new channel appears. After the fact you can still answer "when did this last run and what did it do"; a cleanup nobody can audit is indistinguishable from one that never happened. Three states stay apart: never swept (no record), swept and found nothing (zeros), and a record that will not parse (treated as no record, because reading a corrupt note as zero would report "nothing was cleaned" while the truth is that we do not know). The source list pages at twenty rows, with the page index clamped so a refresh that shortens the list cannot point past its end. Not done, and said here: bulk removal, a server-side page for very large stores, and sweep history beyond the most recent run. The previous version did inverse folding for real: give it a backbone and it designs sequences, with the fingerprint that lets anyone recompute them. Measured on this machine (CPU, 8 GB): crambin's 46 residues in 0.29 s, two designs scoring 0.8519 and 0.7794 at 0.50 and 0.57 recovery against the input, and the native chain the engine parsed matches the PDB. Two independent runs with the same seed produced byte-identical FASTA — that is what makes a design citable rather than merely plausible, and the result carries the seed, model, temperature and the sha256 of both the structure and the checkpoint so someone without this app can reproduce it. The engine is not bundled (6.7 MB of MIT weights, but ~740 MB of torch): the app declares it, decides availability from whether this machine has it, and lists each missing path instead of saying "not installed". Not done, and named here: no weight download, no environment provisioning, binder design and MD are still open, and designs do not flow back into the structure workbench. The previous version swept the copies nobody references — once per start, and never on a register it could not read. The previous version deleted a stored copy only when its source was removed, which left a gap: a hand-edited register, a restore from backup or a crash between save and delete could orphan a copy that nothing would ever collect. The sweep closes it, with the one guard that matters: the register is read first and an unreadable register aborts the sweep with nothing deleted — an unreadable register looks exactly like "nothing is referenced", and acting on that reading would delete the whole store. Two smaller conservatisms: only digest-shaped names are removed (a file someone dropped in that directory is not this store's copy), and a copy that will not delete is reported by its absence from the pruned list rather than claimed as cleaned. It runs asynchronously at start so a slow disk cannot hold the window, and logs one line when it does something. Still open, and named here: there is no manual trigger, the sweep matches on the digest in the filename rather than re-hashing contents, and there is still no bulk removal or pagination. The previous version added the action the store was missing: removal. A registered source could not leave before, and a URL's stored copy only ever grew. The two facts are handled apart — the row always leaves the register, while the copy is deleted only when no remaining source still names that digest, because two sources can hold the same bytes and deleting the file for one would break the other's search. The response reports which digests were pruned, so a caller never has to guess whether the disk changed. Both the model (a fifth tool, with an unknown id refused by name) and the window (a remove control on each row) go through the same store call and the same refusals. Measured on a real window: register → search → remove,1 passed (7.6s), the row gone from the list and the panel saying how many copies were pruned. Not done, and said in the same place: no bulk removal, and copy cleanup happens by reference on removal rather than as a periodic sweep — an orphaned copy still sits in the contents directory until that sweep exists. The previous version gave the knowledge store a window of its own. Settings gains a Knowledge panel: the sources this workspace registered with their kind, location and the first bytes of each sha256; a way to register a file, a folder or a URL; and a search that returns each hit's location down to the line, its digest, and how it was matched. The window and the model's tools go through one module and one set of refusals, so what a reader sees cannot drift from what an agent gets — a refused path still says why (source-outside-roots), and three kinds of absence stay apart: a record that cannot be read is never shown as "no sources",staleis marked separately from "could not re-read", andindexed/pendingsit beside the status so "nothing matched" and "some sources were not read" are different sentences. Measured on a real window: open the panel, register a real file, search TP53 —1 passed (6.1s), with both digests on screen (the whole 85 B file and the cited line) equal to what the test process computed from disk, and the citation's:2-2line range visible. The 26 text keys are written in all nine languages rather than filled with English. The previous version made searching a URL stop meaning re-crawling it. A URL's body is stored at registration under its own sha256, and a search answers from that copy — no network, no waiting, and a slow origin can no longer fail a query. The two paths are deliberately kept apart: the search reads the copy while the citation verifier still re-fetches, so changed content is stillstaleand an unreachable origin is still the third stateunreadablerather thanfresh. Measured against a real HTTP server that is really closed after registration:status: okfrom the copy, thesha256equal to an independent hash of the body, the verificationunreadable— and while the origin was still alive, not one fetch happened during the query. A registration that predates the copy heals once on the next query, so upgrading does not silently turn existing URL sources into "unreadable"; a source with neither a copy nor a reader remainsnot-indexed, never "no matches". The previous version gave the knowledge store a surface the model can actually reach. Four tools —knowledge_search,knowledge_register_source,knowledge_list_sources,knowledge_render_wiki— sit on a capability that is granted per session, behind an RPC whitelist with exactly those four methods, revoked when the session ends. Every result carries the sha256 of the exact bytes it was taken from, recomputed on the read side, and a location precise to the line; an empty answer never collapses three different findings into one —ok,no-matches, andnot-indexedstay apart, so a source that could not be read is never reported as nothing being there. Retrieval is lexical, and every response says so (mode: lexical,semantic-source-unconfigured), withmatchedBynaming per row whether lexical, semantic or both lists produced it — a row both found readsboth, never one coincidence dressed as agreement. The four tools hold separate identities in the permission catalog, so a grant can hand out search without handing out registration. Measured on a real window, not by a script asserting itself: the prompt named no tool, only a folder and a question, and the model chose register → search → render by itself; the digest it came back with for the cited line (5c47f7fa…564b) and the one the rendered document carries for the whole file (c60a2637…538d, 85 bytes) each matched an independent recomputation, and two runs of the same document are byte-identical. Also in this version: citations export as BibTeX and RIS and are persisted on the record with their time and session; a session's conversation is stored once, with the three projections rebuilt from the graph on read; chemical structures are recognised only where two mutually independent engines agree; a library id read off a figure is bound to the record actually fetched from the library, never to what a model asserted; and a vector-drawn figure becomes a candidate without a single criterion being relaxed. What this version does not do is said in the same place: a URL source is re-read at every query, so it cannot be searched offline yet — the next piece is a copy taken at registration for search while verification still goes to the network, because a cache that answers the verifier can only ever vouch for itself; and the knowledge store still has no window of its own, so registering and searching currently go through the model's tools only. The previous version made progress a matter of evidence: a session's goal moves only on a reading the app itself resolves and fingerprints — a false fingerprint is refused by name with the record untouched — and the window shows each criterion either carrying that reading or sayingunmet. Beside it, a knowledge store fingerprints every source it registers (file, folder or URL; the digest is of the response body, so the same content through a redirect stays one source), marks a resultstalewhen its bytes moved, keeps "could not read" as its own third state, and renders a folder as a byte-deterministic document whose every line cites the file and its current digest. Retrieval is lexical only, and says so on every response. multi-agent orchestration and delegated subagents · Python and R runtimes with audited notebook runs · artifact versions and lineage · reproducibility verification (isolated re-run + byte-exact per-file comparison + environment fingerprint) · the review-to-evidence loop · a nine-language interface · a signed specialist marketplace (24 Chinese-medical packages, 18 skill packs) · 31 connectors (282 tools: gnomAD, Ensembl, UCSC, STRING, PubMed, OpenAlex, IEDB, …) plus ENA run metadata that reports the bytes and MD5 each archive declares instead of downloading it · PDF text, tables, figures and captions with page-level geometry (pdf_tables,pdf_figures) · gene-set enrichment recomputed locally from the integers a service reports, from two services (g:Profiler and Enrichr) · a provider save gate whose runtime availability is kept in sync, with an actionable next step when a provider is unreachable · global search filter sets saved by name and named on the evidence line · NCBI reference sequences with SHA-256, byte count and base count · global search ·.sciencesession packages that carry the literature PDFs behind their citations · a citation-style layer (nine built-in styles plus CSL import) · private session bookmarks pinned to an exact artifact version · session forking that carries a count first and a provenance link back · a session information card · and a command line whosedoctorreports measured facts while the readiness verdict stays where it belongs. This version fixes what the installed build's own record showed, and adds the first layer of illustration understanding. A read-only audit of the app users are actually running — its own last seven days: 28 sessions, 3,883 completed activities, 126 problem activities — ranked the friction by signature, and the fixes follow that order. An agent that did the work and reported a plan stepcompletedwas refused for never having bookkept a start, and a refusal about dependencies never named the one to do first; a completion reported without a start is now legal and still passes the same dependency gate, and the refusal says which step to start with. A whole tool family was silently unattached whenever a server name's hyphen met a framework that folds it to an underscore — the model held the correct name and gotNo such tool available— and both sides are normalized now. The app could be configured with a proxy and still fail its own 299 MB weight download with a barefetch failed, because main-process fetches ignored both the app's proxy setting and the operating system's: the download now runs on Chromium's stack, and its failures name the cause and the next step. And a guard that refused a path outside the project described the data root as "the project folder itself", which sent the agent to the wrong place; it now lists the roots it actually enforces. Then illustration understanding, first layer: a figure is classified as chemical structure, protein structure, chart or generic schematic with every vote naming its own evidence, and evidence that is not enough refuses by name instead of letting a generic description impersonate the three classes it must not cover; what a class may derive is stated per class — a chart yields the numbers printed inside its box, each with the geometry of the line it came from, and the rest refuse by name. Deriving lands on the existing provenance surface as a third observation kind carrying an input fingerprint whose recipe is published for recomputation. One real reading contradicted the design and is recorded as such: on this machine's real PDFs every figure candidate was unclassifiable, so the next version widens the evidence instead of loosening the rules. The previous version shipped the assets the parsers will need, and made them arrive verifiable rather than trusted. The manifest that names each local parsing model ships inside the app and is read offline by the app's own validator — field by field, each refusal named (a weight url that is not https is one), because a manifest that reaches users cannot be fixed by a later commit and therefore has to be right before it ships. A model is fetched only when a reader asks for it: streamed to disk, hashed as it arrives, and kept only if those bytes match the published SHA256 — a mismatch or a failure removes the temporary file, because a half-written weight that a later run reads as "cached" is the one thing this path exists to prevent. Three digests do three jobs and are labelled as such: the publisher's md5 from the source record (a cross-check), the sha256 over the bytes we distribute (what the download verifies), and the hosting release's own server-side digest (a third party's, so nobody has to trust our transcription) — all three agree on 298,662,033 bytes, and an independent recomputation on this machine reproduces them. The window lists what the manifest carries — revision, publisher, licence, size, digest — and keeps its download button disabled until the reader ticks that they understand it pulls 299 MB over the internet; the reply is the app's own sentence (downloaded and verified,already cached, or a named refusal), and the list is then re-read, so what is on screen is a fresh reading rather than a guess. A manifest that will not validate names every problem beside its entry and shows no button at all. What this version does not do is said in the same place: fetching a model here does not make anything parse with it yet, because wiring these assets into document and figure parsing is the next piece of work. The previous version turned a method into a product: a recorded notebook run compiles into a callable skill, and a third-party public repository can be read, checked and deployed as a plugin — with the deployment atomic, the execution gated by the protection policy, and nothing written or started before the reader confirms. The previous version widened the read side, and made every new number carry what it is relative to. Pairwise LD arrives with its panel and sample size, an assay's list with the source's own total, a per-submission record with who said it, a miRNA-target table with its header verbatim and its unrecognised columns named, a file listing with declared bytes and no download, a PDB entry's residues in both numbering schemes at once, and one more provenance step that names which later run changed a value and how — under a hash you can recompute. The previous version made a notebook run reviewable before it happens, and named what a run did to your files. A cell is checked against four named rules —fs-delete,fs-overwrite,shell-execandlarge-allocation, each carrying its own rule version — and a flagged cell asks on the very card the app already uses, so one decision costs one card: the card lists the rule, its version and the line of code that matched, instead of a sentence saying that something looks risky. Declining is concrete — no kernel is provisioned, no code runs and no run record is written — and what the panel then shows uses that same rule vocabulary in all nine languages (Notebook run declined: deletes files (fs-delete) — no code was executed.), where it used to show an engine class name wrapped in a transport envelope. Every run carries a risk receipt — verdict, rule-set version, code hash and the hits — persisted with the run and discarded wholesale when it fails to parse on read, because half a receipt is worse than none; and a record with no receipt reads as not assessed, never as no risk. Beside it, dependency lineage now works at the alias and in-place-mutation level across runs: each stale-input reason names its kind (name-rewriteordirect-mutation) with the mutation's line and the alias chain it came through, and the content hash a lineage row needs is computed on read, where anyone can recompute it — 1,412 of 1,412 rows on this machine's real data, against 0 before. The in-session file-change review records a real sha256 on both sides of every change: the before side is observed when the run starts, under a bounded sweep (64 MiB of entries and bytes;too-large,not-recordedandunreadablenamed one by one, never chained from an earlier run's after), so the panel can print abefore → afterpair you can recompute yourself. Real-machine readings this version: the approval card on a real window (1 passed (11.9s), withNotebookRunDeclinedError: … fs-deletein the app's own log and zerormtreerecords after declining), the sentence the panel's own terminal prints (1 passed (8.8s), matching the strip character for character and carrying neither the engine error nor the IPC envelope), and a real 182 MB session workspace whose before and after hashes equal an independent recomputation — with a 6 MB weight file namedtoo-largeon both sides, not one byte of it read. Both notebook specs then ran on the certification lane's own machine as well (103 specs: 90 passed, 1 flaky on an unrelated PDF spec that passed on retry, 12 skipped), so the card and that sentence are checked twice, on two machines. The previous version added two working lines. Structures: open a PDB/mmCIF or a predicted model in a 3D view, select a residue range, and what enters the conversation is that selection's own identity plus a fingerprint you can recompute — structure id, chain, residue range, representation and derived sets, never a timestamp and never the name — so "this region" means the same thing tomorrow; a Chinese sentence ("把 145 号半胱氨酸 5 埃以内的残基标出来") becomes a real selection with the parameters it sent shown on screen, and a measurement is either taken on atoms that exist exactly once in that structure or refused by name (a residue-level distance is refused outright, because which atom pair to measure is ambiguous); a selection can be named and coloured, and the name travels in the context block but not in the fingerprint (the same region may be a binding pocket today and an active site tomorrow); exporting the view goes through the approval card — refusing writes nothing at all — and the exported image carries that region's fingerprint and name inside it, while those same text chunks are stripped from anything handed to a model; fetching a structure by PDB or UniProt id is decided by the deny-list / allow-list / protection gates, and the renderer can never name a host or a URL; and prediction-engine outputs land underpredictions/<engine>/, where the view labels them with engine, output kind and licence — saying "not an experimental structure" in words. Literature: translate a PDF paragraph by paragraph with resumable checkpoints, so reopening the app never re-translates a finished paragraph — the evidence is that not a single request goes out; read it in source / translation / side-by-side, where both columns share one set of rows so there is nothing to keep in sync; per-page state tells "this page has no text layer" apart from "not translated yet", with four numbers beside it for checking; per-paragraph terminology deviations are named against the app's own MeSH-sourced table and judged only where a translation exists; clicking a translation jumps back to its own line in the source; references are kept verbatim and shown as kept rather than as missing; and the exported side-by-side file carries a recipe, the source hash and both counts in its header, so you can count its pages and paragraphs yourself. Real readings this version: a real 7-page / 39-paragraph PDF (its seventh page has no text layer, and the panel says so), one real translation (deepseek-v4-flash, 20.0 s) that stored its record and surfaced a genuine terminology deviation — the prose came back as 阿司匹林 / 随机对照试验 / 荟萃分析 while the journal nameLancetstayed in Latin — and a prediction-structure load that was refused with no network and no file because nobody could answer the approval card. The previous version asked what examination was done — 20 terms for imaging, endoscopy, pathology and lab procedures (磁共振成像, 内镜检查, 活组织检查, 心电图, 高通量测序 …), each with its English side read from MeSH and the descriptor recorded on the row.磁共振成像 脑卒中now goes out asmagnetic resonance imaging stroke(PubMed 49,165),all:磁共振成像asall:magnetic resonance imaging(arXiv 498,230) and超声心动图 心力衰竭asechocardiography heart failure(OpenAlex 284,090); before, the examination half of such a question was refused by name with nothing sent. Six rows that had landed undermethodmoved to a newprocedurekind — measurement, not study design, and the kind travels to the agent, so the label has to mean what it says — with their English sides and descriptors untouched. All 100 sourced rows still verify against NLM. The previous version asked a mechanism question in Chinese — the term table now carries 33 subject-matter terms (apoptosis, autophagy, ferroptosis, epithelial-mesenchymal transition, exosomes, ubiquitination …), each with its English side read from MeSH and the descriptor recorded on the row.细胞凋亡 肺癌now goes out asapoptosis lung cancer(PubMed 31,551) andall:铁死亡asall:ferroptosis(arXiv 6); before, the mechanism half of such a question was refused by name with nothing sent. The batch's design, rows and readings are the autonomous executor's, replayed here from the recipe its own evidence note lays out after a stand-down — the two read-source cases now share one helper so a new sourced kind cannot land with weaker checks than the ones beside it, and the whole table still verifies 86 of 86 rows against NLM. The previous version made the table's provenance checkable by hand — every sourced row claims its English side was read from a MeSH descriptor, and until now that claim could only be trusted or checked manually.node scripts/verify-mesh-sources.mjsnow resolves each descriptor at NLM and compares its preferred label with the term the row actually sends; it reports a row that cannot be fetched differently from a row that disagrees, names entries that are not in the table at all, and exits non-zero on any mismatch. Run over the whole table on the day it landed: 53 of 53 rows matched. It is deliberately not wired into CI — a lane that goes red because a third party is slow teaches people to ignore it. The previous version asked the design half of a Chinese clinical question — the term table now carries 30 study-design, statistics and omics terms (队列研究, 随机对照试验, 荟萃分析, 机器学习 …), each with its English side READ from MeSH rather than translated here: the MeSH descriptor is recorded on the row (mesh:D016449), so anyone can resolve it and check the term. A Chinese question is usually a design word plus a subject, and without these the design half was refused by name —随机对照试验 阿司匹林now goes out asrandomized controlled trial aspirin(PubMed 9,072) andall:随机对照试验asall:randomized controlled trial(arXiv 401,679), where before both were refused with nothing sent. The Chinese half is the term as Chinese medical literature writes it, and the evidence note says so per row: only the English side is an authority's. The previous version answered Chinese questions on arXiv — and it earned the rewrite policy the hard way. A single probe came back as HTTP 500 with a valid-looking Atom feed whosetotalResultssaid 1: that1was an error entry counting itself, and reading it naively would have reported "one Chinese match" for a query that never ran. The app already detects that feed and raises by name, and this version verified it with a real reading rather than by reading the code. The decisive comparison then settled the policy:all:阿司匹林returns 0 whileall:aspirinreturns 42, so arXiv indexes Latin text and refuses unmappable Chinese by name with nothing sent. The rewrite is done IN PLACE — arXiv's query language carries meaning in punctuation, so composing it from space-joined pieces would turnti:阿司匹林intoti: aspirinand move the prefix off the term it applies to. The previous version answered Chinese questions on OpenAlex — the app's Chinese term table now rewrites queries for the all-disciplines literature source, and the policy differs from PubMed's because the two sources behave differently, measured rather than assumed: PubMed indexes Latin text (term=阿司匹林returns zero whether or not the evidence exists) while OpenAlex indexes and answers Chinese (search=阿司匹林returns 11,867). So OpenAlex gets the rewrite where the table knows a term — the English term of record is far better covered (aspirin443,778 against 11,867) — and searches what it does not know as typed, naming those terms in the result instead of refusing the query. Only a query made of nothing but function words is refused, by name, without sending anything. The previous version let you check a reading from the window — every reading in an artifact Version's provenance now has a button that re-issues the request it came from and reports what came back: the same bytes, different bytes (both digests are shown), or a refusal that names its reason. Re-issuing is the only thing that can turn a recorded digest into a checked one, because the digest is computed over the response bytes and the bytes are deliberately not stored. The request never carries a URL: the window sends a session and a digest, and the main process looks the reading up among the ones it recorded, so the capability is exactly "re-verify a reading this app took" and cannot be used to fetch an arbitrary URL. A request that was not a read, a URL whose credentials were stripped, and a record that predates the fields a faithful re-issue needs are all refused by name rather than re-issued approximately. The previous version said which run a reading came from — every reading in an artifact Version's provenance can now name the run that recorded it, and the one taken by the run that produced that Version is marked on screen. The id comes from the main process's own session state, never from a caller-supplied argument: run identity is not something a call gets to assert about itself. When no run was executing, the reading carries no id at all rather than a guessed one. And the window promise from the previous version is now kept only where it can be: if the Version's evidence carries no readable creation time, the panel says the readings are the whole session's instead of printing "before this version was written" over a set it never filtered. The previous version made the readings window real — the panel said "what this session read … before this version was written", and the projection listed every reading of the session regardless: a version written at 10:00 showed readings taken at 15:00 under a label that claimed a window. The window is now enforced against the version's own created_at, and readings that fall after it are counted and named on the panel instead of being silently absent — a stamp that will not parse is counted too, never quietly promoted into the list. The certification spec gains a seed on each side of the window and asserts that exactly one reading is listed, so the promise is checked on a real window as well. One correction rides along: the previous body said the panel's real-machine reading had not been taken, and it had — from the certification lane, with its own printed reading. The previous version paid for the readings chain only where it is returned — the journal is one file read per session, and three paths were paying it for answers with nowhere to put it (a reproduction verdict, a code reconstruction, the bulk card listing), so each now says it does not want it; and a session's parallel calls no longer overwrite one another — eight overlapping writes used to leave exactly one reading behind while the dropped count still read zero, a loss that looked like completeness, so writes are serialised per session. A verifier nobody called was removed with the rest of the unconsumed exports, and the shape guard it relied on now runs where the journal is read: a digest nobody could recompute is reported as unreadable rather than shown as a reading. The previous version put that chain on the screen — the artifact-Version provenance panel gains a Readings section: every reading the session recorded, with the service and tool, the request method and URL, the status, the byte count and its sha256; the published recipe it is computed under, read straight from the constant instead of retyped; and how many older readings the cap dropped. The three missing states stay apart — no journal, unreadable, and empty — so an empty list is never drawn where "nothing was read" belongs. The panel asks for the readings on that one read, and the bulk card path still pays no file read per card. **The previous version walked from an artifact Version back to the readings — provenance now lists what was read in that session: the service and tool, the request, the response byte count and its sha256, under a published recipe an independent implementation reproduced byte-for-byte here. The attribution is carried on the value — session plus time window, never per-run causality, because the connector layer knows the session and nothing finer — and a missing journal saysnot-recorded, an unloaded one saysnot-loaded, a corrupt one saysunreadable, so an empty list is never read as "nothing was read". The GB/T 7714 block for electronic resources also gains the issuing body, which is the shape a Chinese evidence item actually takes ("中华医学会, 2021"); the slot opens only when the record carries a publisher, so a record without one keeps its exact bytes. The previous version named what a call leaves behind — the connector approval card now says when a call creates state on the service rather than only sending a query: the tool that registers your gene list on Enrichr's own servers is marked as such and the card prints that the entry outlives the call and cannot be removed from here. The card used to describe only how long the APPROVAL lasts ("applies to this call only"), which reads as how long the call's effect lasts. The declaring set is pinned by a test in both directions: the twenty-odd POST-only connectors here merely carry queries and are asserted to stay unmarked, so the line keeps its meaning. The previous version answered Chinese questions with evidence — a Chinese term is mapped to the language the sources index before anything is sent, and the rewrite is reported in full: what was recognised, which words were dropped, and the exact query that went out (阿司匹林 returns 80,708 matches and 阿司匹林治疗高血压 4,910, against a count of 0 for the raw Chinese, which the live service answers with an empty result whether or not the evidence exists); Chinese the table cannot map is refused BY NAME with nothing sent, so a zero-match answer can never again be read as 'no evidence'; the connector panel gains zh_medical_terms, which resolves a Chinese term the app has never seen to a citable entity and says which label or alias it matched on without collapsing ranked candidates into one answer; and every connector reading now carries a fingerprint — service, request, response bytes and sha256, under a published recipe, reproduced byte-for-byte here by an independent implementation. The previous version made a stop say only what it did — a remote host that cannot be reached gets a refusal that carries ssh's own sentence and exit status, and its job row staysrunninginstead of being written ascancelled; a stop that does arrive takes the job's whole process group, not just the launcher, and the row readscancelledon the way back out. A host with neithertimeoutnorgtimeout— stock macOS — no longer fails the job outright: the workload runs, and the host's stderr carries one named line about the wall-clock limit the app's own budget stands in for. It also adds IEDB immune epitopes, whose every record carries its assay method, MHC restriction, quantitative result and citation, whose empty results name the family that was empty and say this is not an absence of response, and whose truncation flag comes from asking for one row more, because the service reports no total. The previous version made a named environment's packages reachable from the window, and stopped a refusal from claiming an approval that never happened — the Settings Packages dialog adds to and removes from a named environment for real (a distribution installed through it is importable by that environment's own interpreter; one removed through it is gone from the inventory the dialog re-reads), with the app-managed default's inventory compared row by row before and after so a silent retarget cannot pass, and the default itself still additive-only; and a remote run refused by the execution-protection policy now reportsprotection_refused— saying that nothing was submitted and nobody was asked, and where to change the policy — instead of the oldApproval denied, which sent readers after a grant that could not have helped. The previous version inspected what arrives from elsewhere, and let you take back what was merged, delivered or imported — an RO-Crate someone hands you is read in place and checked requirement by requirement, writing nothing and touching no byte of your project, with its payload bytes recomputed too so the checked rule set is the same one the export side applies; an imported skill can be duplicated into a skill of your own that you can edit, while the copy you imported stays as it was imported; the alias a journal merge left behind can be released, with the metrics and references that merge moved left where they are, as the window says before you act; the message centre deletes one notice or clears the whole inbox, stating that your conversations are not deleted and that it cannot be undone; the engine panel reads itself out — seven engines, each with its availability and a named reason, and no download button is offered where no published checksum exists to accept it; and a session replay step can be asked about, the answer assembled locally with the field behind every sentence cited, and the record's silence reported rather than filled in. It also re-read two gaps that had been carried as product defects: the journal-import panel in Settings does render its row-by-row outcome (the earlier reading came from a locator that matched a toolbar button by substring), and the progress detail fields do reach the renderer — both sides of the broadcast read the same message object. The previous version carried the evidence with the package, and gave an interrupted run its own name — a.sciencesession package that lands in a fresh project now writes its citations, review findings, review conclusions and human-pinned evidence into the receiver's own tables, and the certification run compares all four dimensions row by row against the sender's source instead of asserting a hand-written 1 (the receiver is an empty project, so every landed row can only have come from the package); a notebook cell in flight when the app went away is recorded asinterruptedwithapp-terminated— the domain rule has said from the start that this is not a failure, "the code may have been fine", and the write path now agrees, including when the process tree is killed from outside while the app is still shutting down (the crash path consults a shutdown latch, and the shutdown refuses in-flight runs immediately while still letting a queued persistence write finish); a scanned PDF says how many of its pages carry no extractable text rather than only naming the document; a screening batch names the kind of failure it hit so a reader can tell whether to re-run; a failed handoff prints which step to resume from; a script the app truncated is labelled as truncated instead of being shown, and exported to.ipynb, as if it were whole; andpdb_search_by_sequencesearches the PDB from a bare sequence, without a UniProt accession. Previously it added a way in to what was already built — a managed artifact preview can open the file in its default application or reveal it in the file manager, with the opener resolving the path through the repository in the main process (anything outside artifact storage is refused by name rather than quietly opened, and a refusal is shown in the handler’s own words); a finished remote job shows what it produced, listing the featured outputs and every file left on the remote host with its own size and reason, saying how many more the payload did not list instead of showing a subset, and printing a failed harvest beside the workdir that is kept so the files can still be fetched by hand; the message-centre badge lights up on its own when a task finishes, without the bell being opened first; and three settings channels that had no handler on the Electron bus — which left the function-model panel saying it could not read its own settings, with every button inert — now answer. Previously it added index coverage you can read — global search reports how much of the corpus the incremental index holds (indexed), how much changed since (pending) and when it was last measured, with an "Index now" step that runs one bounded tick and answers with the reading it produced; on the real window a restart does not moveindexedbackwards, and deleting the index directory does not turn the corpus into "nothing found" — the next query rebuilds it on disk and the files are still returned (3 hits), while the coverage block keeps reporting the last measured tick instead of a reading taken mid-rebuild. Together with two values for one year printed side by side with their own sources, a batch of interface copy localised in all nine languages, and a measured contrast ratio for text on the brand colour (4.144:1 — below the 4.5 body-text floor, and pure white only reaches 4.330:1, so the gap is recorded rather than papered over). Before that it added importing an environment from an external lock file — every package verified against the md5 the lock publishes, and a single unverifiable entry builds nothing — together with named environments that can be listed, used as a notebook runtime, or removed, with a running kernel's hold on one refused by name rather than reworded. Earlier it added journal entities — an ISSN, or an exact normalised name, resolves a journal; every impact factor and CAS partition carries the year and the source it came from, withUnknownwhere none was imported (never a 0); two spellings of one journal merge only when you say so, and the old spelling keeps resolving; and metrics import from CSV/TSV with one named result per row. The previous version added a reason for a page that yields no table — the extractor's own measurement, with the counts and floors behind the decision, and a named throw when a shape passes every gate — together with rotation-normalised table reading (text drawn at an angle is measured in its own frame, and the page whose coordinates were rewritten says so) and a named reason on the turn path for a turn that never reached the skill selector, so the trail answers that question instead of staying silent. The version before that added per-run file evidence in three states — read, written, and not captured with a named reason, so a list with no explanation cannot be expressed — together with content-addressed storage that keeps identical bytes once and measures the space it saved off the filesystem (an in-place tidy-up decides by hash, not file size) · explicit states for the official skills catalog, a recomputable quality score on every skill row and an import check that says whether a skill's content still matches what was imported · and a real function-model probe round-trip reporting measured elapsed time and the skill it selected. Earlier it added an execution-protection matrix (four execution surfaces × the filesystem-write and network-allowlist axes, each row naming its gap and the fix path) with unprotected remote runs denied by default · global search scopes split into uploads and generated files, each scope chip carrying what that scan actually covered and showing a dash — never a 0 — for a scope that was not searched · advanced search filters (time window, ordering) that print the request's own condition string on the results, with a re-run check that reports the evidence as unchanged, changed or not found · per-purpose function models whose row names the model in force, the service it comes from and the built-in path used when it is unavailable, plus a real detection round-trip reporting measured elapsed time and token usage and a retrievable trail for every fallback · and a locally recomputable skill trigger-quality score with the missing pieces named, beside a per-reader skill availability matrix whose always-on and globally-disabled entries are shown as locks with their reason · literature and journal records that keep their own history — a replaced PDF stays in view, a reference's notes are writable and survive a restart, and a journal metric corrected by hand keeps the old value beside the new one, each with its own source and year. -
✅ Shipped and machine-verified: A sweep now leaves a reading behind. Every completed sweep records when it ran and what it found — scanned, kept, removed, still-failed, bytes freed — and that record rides along with the source list the panel already fetches, so no new channel appears. After the fact you can still answer "when did this last run and what did it do". Three states stay apart: never swept, swept and found nothing, and a record that will not parse. The source list pages at twenty rows with a clamped page index. The previous version did inverse folding for real.
-
🚧 Partly done: Retrieval is lexical only in this version — no semantic source is wired, so every response carries
mode: lexicaland names the degradation and every row sayslexicalOnly; the semantic step is left for the version that actually has an embedding source rather than pre-wired as dead code. The goals panel is read-only by design (creation and progress belong to the session's own tools, which can name a reading the app resolves). Citations are not yet persisted: what this version ships is the re-checker and the per-row marks. Windows builds are not code-signed — the installer works, but Windows warns about an unknown publisher at install time; we say so here rather than let it surprise you, and signed Windows builds wait for a certificate and a signing identity. CSL import runs a fidelity probe against each document — of eight real published styles, five pass and three are labelled drafts with the missing piece named (their author macros branch on record types we do not model yet). ENA is metadata and reachability only: no FASTQ is downloaded. The shared diff viewer covers artifact comparison and tool-edit diffs; skill updates and session-package record verification are not wired to it yet. Distribution has just started — both READMEs lead with a result the product produced and nine languages now exist — but the download site is not deployed yet and the repository is still young. Two things remain unshipped: the skill-marketplace slice (K1a) is archived, not shipped — the official skill catalog could not be reached from the build machine (the CDN metadata path returned 404, the raw host timed out) and the marketplace protocol root carries specialists only, so no consumer-less code was written and the slice waits for the version that publishes the catalog — and two of the five real-machine criteria for per-purpose models are still uncertified, and the previous version isolated exactly why: the narrow model call is only reached on a bridged (Codex) turn path. A real codex session was established for this version — through the app's own managed install inside a throwaway root (303 MB, nothing on this machine changed) — and a live turn now records for itself that it had no skill-selection bridge (bridge-unavailable, readable in the trail), which is the first of those criteria certified on the turn path. The remaining one — 'the answer on a live turn really comes from the configured vendor model' — is still uncertified, and the previous version isolated why the earlier attempt could not even reach that question: the function-model host handed to the runtime was being dropped at the composition layer, so the configured model was never consulted on a turn and no trail was written. With that fixed, a live turn on the same isolated instance records a real round trip andused-model, and the fallback path that used to claim the model had answered now recordscall-failedby name — but the party answering in that live reading was a stub endpoint, so 'answered by the vendor model' stays uncertified. Also open and named: the app's own detection accepts only its managed adapter, so a user-installedcodex-acponPATHis not usable in-app yet — a detected-but-unusable adapter reportsCodex native executable not found. The two units this narrative previously named as not started are shipped in this version and verified on a real machine: an imported session states where it came from (imported from X / exported at Y / not verified on this machine) and stays read-only, and a data-root move that never finished is re-discovered from its marker on disk after a restart, so it can be completed or discarded without the window that started it. This version also gives a user-added MCP server its whole action surface — its detail page lists the tools the server itself advertises with per-tool permissions, the skip-approvals switch holds, a connection test separates "the server did not answer" from "the server advertises nothing", a signed-in server can be signed out, an unusable one says why on its own row, and the bulk buttons cover these servers too — beside the runtime surface: installing packages into a managed environment, binding and switching a notebook's runtime, a prep overlay whose Cancel works, and a card that says where the app-managed runtime comes from before anything is downloaded. -
🗺️ Planned: explicit guidance when a plan is resumed · a wider serverless GPU surface.
Rules we do not relax: no model weight is downloaded without a published SHA256; an imported style or skill without a licence is not installed; a field the record does not carry stays empty and is named — never filled in on its own; code a model reconstructed is never certified as a reproduction; timing conclusions are only drawn from the same corpus, the same driver, measured before and after (a millisecond assertion that wobbles under load is reported as unstable rather than tuned until it passes).
v2.19.0 — 2026-10-28(框架选择不再被静默忽略;A2A 线归并到这一版)
编号更正(先认):A2A 的卡与 message/send 实际是在 v2.18.5 发的——那是我按「每个可验证改动发一个小
版本」的惯性推的,与计划档 plan-2026-10-24-v2.19-a2a-trajectory.md 的编号脱节。从这一版起,A2A 算作 v2.19
线的一部分;v2.18 系列停在 2.18.5,不再往下发。原定的 v2.18.6(只有下面这两笔)已撤销 tag 与发版材料。
一、缺陷:联合类型有四个框架,两处白名单只认三个
AgentFrameworkId 是 claude-code | opencode | codex | codebuddy,而两处手写白名单只列了前三个:
| 位置 | 后果 |
|---|---|
src/main/settings/repository.ts(读取规范化) |
setAgentFramework('codebuddy') 返回成功,下次解析即被丢弃 |
src/shared/session-persistence.ts(持久化) |
codebuddy 会话落盘时框架 id 被丢弃,读回成 claude-code |
二、修法:一个来源 + 编译期完整性检查
shared/settings.ts 导出 AGENT_FRAMEWORK_IDS 与 isAgentFrameworkId,并加编译期完整性检查:联合类型里
每个 id 都必须出现在列表里,漏一个就编译不过。两处调用点改用它,各自的白名单删除。用例覆盖四个 id 在两处的
round-trip。
三、真机读数
framework selection { forced: null, stored: 'codebuddy', resolved: 'codebuddy' }
framework selection { forced: null, stored: 'codex', resolved: 'codex' }
- codex:会话
01a17e35,fw=codex、servers=14、run 退出码 0(回合答ok)——第一次可复现读数; - codebuddy:选择已正确生效,但
codebuddy --acp在这台机器上握手不成立
(ensureConnected: connect failed;手工initialize两秒无回应)——阻塞在它的 ACP 运行时; - 机检③ 因此记 3/4(opencode / claude-code / codex)。
四、新增诊断
[settings:backend] framework selection { forced, stored, resolved }:让「设置没被读到」与「读到了被改写」
不再同形。这次正是它把范围从猜测缩到一行读数。
明确没做(都是「刻意不做」,不是漏了)
tasks/cancel:task-api 与 runner 里没有任何取消通道,要动 ACP 提示的生命周期,属产品级改动;message/stream:可做(轮询式 SSE),但计划档未要求;卡里streaming: false是如实边界;- 跨机配对:A2A 的卡与
/a2a目前都走本机 token,跨机要先设计配对授权; - codebuddy 读数:等它自己的 ACP 握手可用。
⚠️ macOS signing status: unsigned — the macOS packages in this release carry only an ad-hoc signature (no Apple Developer ID credentials are configured), so Gatekeeper warns on first launch and in-place auto-update is unavailable on macOS. Install the DMG manually (right-click → Open, orxattr -dr com.apple.quarantine "<app>").