Changelog
Format follows Keep a Changelog.
History before 2026-08-28 is not reconstructed — this file starts where a
narrative commit history and this project's day-by-day session logging
overlap, not at the repo's actual beginning. Each line below points at the
commit that carries the reasoning rather than repeating it; git log
--oneline <sha> on any entry shows the full message.
Unreleased
[0.5.0] - 2026-10-06
Hosted NVIDIA NIM embeddings that rank correctly, attest reembed to switch a
database's embedder in place, and an install that pins the database the agent
actually used.
Added
- NVIDIA NIM embeddings.
EMBED_BASE_URL=https://integrate.api.nvidia.com/v1withEMBED_MODEL=nvidia/nemotron-3-embed-1bnow sends NIM'sinput_type(query or passage) andtruncate: END. Without themnemotron-3-embed-1bstill answers 200 but ranksevals/reaction_cases.jsonat AUC 0.625, against 0.994 at 1024 dims with them (measured 2026-10-06).EMBED_INPUT_TYPE=1turns this on for a self-hosted NIM. TheEmbeddersends raw text plus the role instead of gemma's task prefixes. (#43) attest reembed. Switches an existing database to the configuredEMBED_MODEL/EMBED_DIMSin place, keeping personas, clicks and the library: it checks the new embedder answers before touching anything, backs up, and rebuilds only the two vector tables. It is resumable and takes--force. Measured on a copy of a live 20,013-item database: 23 minutes through NIM. (#43)
Fixed
- A non-OpenAI-shaped embedding reply (for example, Ollama's native API)
raised
KeyError: 'data'. It now raises an error that names the host and quotes the body. (#43) - Ingest no longer reports an embedder fault as N broken feeds. A failure
is logged as "embedding failed for
". A second feed failing the same way is reported once, and the remaining feeds are skipped. (#45) - The model and width mismatch refusals name
attest reembedinstead of "re-ingest into a fresh database". (#45) attest installpinsATTEST_DBto the database that holds the data. Before v0.2.2 the agent's tools wrote~/hermes.dbwhile ingest wrote the checkout's, and v0.2.2's pin chose the checkout's, which would have orphaned 130 references on one AgentMarkit machine. The install now pins the one candidate holding data. It refuses when two candidates hold data, and reports an existing pin to an empty database while another one holds data. (#44)
0.4.0 - 2026-10-05
Chat on whichever model Hermes runs (opt-in), and a library dashboard export for AgentMarkit's Files tab.
Added
ATTEST_LLM_FROM_HERMES=1: chat on the model Hermes is connected to, whichever provider that is. A hosted machine embeds on a loopback server and chats through the model the customer connected to Hermes, but Attestation never learned what that model was: chat fell through to the built-in Ollama default and asked the embedder-only server forgemma4:e2b-it-q4_K_M(a 404). With the opt-in, the endpoint, model and key are read at call time fromHERMES_HOME(config.yaml,.env; the process environment only for a key variable.envlacks, so the cron refresh works) and follow "Change model". Providers that speak chat-completions with a key (NVIDIA NIM, OpenRouter, OpenAI, DeepSeek, custom endpoints, Ollama or llama.cpp as Hermes runs them) are called by Attestation; ChatGPT sign-in, Anthropic and anything else are called by Hermes' own runtime through a small helper (hermes_bridge.py), so no OAuth token ever enters Attestation. Off by default. Env vars always win; embeddings never follow it. Seedocs/guides/install.md.attest library dashboard: one atomic, 0600library.json(schema 1: totals, 26 zero-filled ISO weeks, top sources, one row per reference, capped and sanitised) for a host page to read;attest install's refresh script runs it as a degraded step. Seedocs/guides/library-dashboard.md.attest install --checkprints abackendsline: the resolved chat and embedding backends (hosts only) and where each came from.
Fixed
- Review round on this branch: a hung Hermes call is now killed at its deadline
(it used to hang the run and the refresh lock), the helper cannot orphan, runs
with a minimal environment and cannot be shadowed by
attestation/mcp;OPENAI_API_KEYis no longer sent to a custom host; keys are redacted from every message; keys are refused over plain http to remote hosts or if not header-safe; the dashboard is bounded at 6 MiB (byte caps,--limitmax 5000) and is one read snapshot that survives a bad byte; a 429/5xx is retried briefly. - Second review round: the helper's parent-death watchdog now starts before Hermes
is imported (a parent killed during start-up used to leave it running), fd 1 is a
private duplicate so nothing Hermes writes can forge or corrupt a reply, non-object
and glued replies are handled, every long string in
auth.jsonis redacted whatever its field is called, and a 429/5xx storm stops the run once instead of holding the refresh lock for hours. NewATTEST_LLM_TIMEOUT(10-600 s). - A chat backend that answers 401, 403, 404 or 410 now stops a tagging run
once, with its reason and where the url and model came from. Only a dead
socket did; a model the server never had was retried twice per item, counted
failed, and printed nothing. The "is ollama running?" hints no longer name Ollama for a hosted backend, and say which variable the URL came from. chat_jsonretries withoutreasoning_efforton a 422 as well as a 400.- The default chat client is rebuilt when the model it resolves to changes (an MCP server lives for a whole session).
0.3.1 - 2026-10-02
The provenance skill tells an agent what to do without the tools and ships executed notebook demos, and the skill installer now copies what a skill ships.
Added
- Executed notebook demos ship with
attestation-provenance. Three offline notebooks (check a draft's claims, which arm won, make your own claim) sit in the skill'snotebooks/folder with their real outputs saved and aRESULTS.mdfor readers who will not open them;scripts/build_skill_notebooks.pyregenerates both by executing the notebooks, never by typing outputs. The skill tells an agent when to offer one.nbformat,nbclientandipykerneljoin the dev group.
Fixed
attestation-provenancenow says what to do without the tools. An agent whose session had noruns.*tools guessed at the CLI (attest claims_check, a tool name) for 17 calls. The skill now gives the four real commands, says a claim checked against an empty ledger isunsupportedbecause nothing was scanned, and thatattest claimsexiting 1 is a contradiction, not a failure. A new test parses everyattest <command>a skill documents against the CLI.- The skill installer copies what a skill ships, from an allowlist. It
synced only
SKILL.mdandscripts/, so anything else a skill ships (the notebooks) never reached a machine. It now syncsscripts/andnotebooks/files with known suffixes, and neverSKILL.md.*(a merge leftover would have disabled the installed skill for good), symlinks, dotfiles or__pycache__. An edited installed notebook is noted by--check, not reported BROKEN. Files removed from a skill are still never deleted from a machine, as before.
0.3.0 - 2026-09-30
The Reading desk: a phone page of ranked papers whose verdicts come back as clicks, the first new human signal the ranker has had since 2026-08-22.
Added
- The reader's bibliography. Papers a reader rates useful, reads in full
or asks about, and papers they ask the agent to save, are kept per persona
in one generated
.bib(ATTEST_BIB_OUT), each with a citation key that never changes and the reason it is there; a removal sticks.cite.save(the 50th tool) andfeed.ask(question="save this", item_id=...)save and remove;attest library biband the hourly refresh write the files. Migration 013.ATTEST_BIB_PATHSentries may now be folders, so an uploaded.bibis read without editing.env. Seedocs/superpowers/specs/2026-09-30-bibliography-design.md. - The Reading desk.
attest desk build|import|refreshrenders today's ranked papers as one self-contained page with Useful / Not my area on each, and records the verdicts a reader gives there as clicks before the next ranking. The ranker had no new human signal since 2026-08-22; this is a gesture surface on the reader's phone. Seedocs/superpowers/specs/2026-09-29-reading-desk-design.md. - Migration 012:
users.feedback_since. The desk's import cutoff, set when a persona is created and whenever its feedback is purged, sofeed.persona_resetis no longer undone by the page's saved verdicts. Existing personas get no cutoff.
0.2.5 - 2026-09-29
Two fixes from one live Discord turn: an answer's links now travel with their titles, and a persona merged away stays merged.
Fixed
- A merged persona kept coming back. Discord prefixes each message with
the sender's display name, so the agent passed
user="Matthew Goldey"instead of the personamatt;personas.mergefolded that duplicate in and deleted the name, and the next read autocreated it again, empty. It regrew three times, and ratings made in Discord trained a persona nobody read from. Merged names are now aliases (migration 011,persona_aliases): reads, ratings and autocreate under them reach the kept persona.attest persona-merge matt "Matthew Goldey"does the merge from the CLI. - A listed paper could be linked to a different paper. An answer named
its titles and
refsheld their urls separately, so the agent paired them up itself -- and in a live Discord turn linked "Learning to Optimize through Solver-Grounded Self-Play" to 2609.12105, another paper from earlier in the conversation, instead of the 2609.34205 the tool returned. Each title in an answer is now a Markdown link to its own url, and the feed skill says to copy those links rather than re-pair titles withrefs.
0.2.4 - 2026-09-29
Feed-ranker first on a hosted machine with no GPU: embeddings from a small CPU embedder on the machine while chat goes to the customer's provider, a first feed list an installer can choose, and a reader who can say what they work on.
Added
- Embeddings from their own server.
EMBED_BASE_URL/EMBED_API_KEYsend embeddings to a different server from chat -- a small CPU embedder on a hosted machine with no GPU (embeddinggemma measured 20 items/s on 2 vCPUs, 380 MB resident) beside the customer's chat provider. Unset, embeddings followLLM_BASE_URLas before. The chat key is never sent to a different embedding host. ATTEST_FEEDSnames the feed list a first ingest subscribes to, so an installer can seed a field's journals instead of the packaged, mostly machine-learning list.- "I work on X" sets what the feed ranks for.
feed.askroutes "I work on", "I'm interested in", "my research is on" and "my field is" to the reader's interests, creating a first-time reader from their own words. On a fresh hosted machine nothing is tagged yet, so a reader created on sight started from a placeholder.
0.2.3 - 2026-09-28
Found by recording a demo of every surface for experimental and computational
scientists: research results carried the wrong DOIs and off-topic CrossRef
hits, runs record could not find what it had recorded, and runs.ask only
understood machine learning's way of asking which run won.
Added
- Demo recordings for every surface, and a narrated film. The demos page
now covers recording results, the reference library, literature research,
a lab-and-simulation ledger example (a catalyst screen and a basis-set
study, numbers labelled invented), real agent turns on each
*.askrouter, and the AgentMarkit install.demos/film/build.pystitches them into one film with a locally synthesized voiceover and captions.
Fixed
runs.askonly understood ML's way of asking which run won. "Which basis set did best?" was asked back ("comparing arms, listing runs, or checking a draft?") because the comparison rule knew "won", "winner" and "sweep" but not "did best", "worked best", "highest", "lowest" or "outperform". Those now route toruns.compare.attest runs recordcould not find what it had just recorded. The scan re-derived each run's family from its file name and ignored thefamily:its config declares, sorecord lr-sweep --arm lr_3e4scanned back as familylrandrecord my_sweepasmy-sweep:--scanprinted no comparison andruns compare lr-sweepfound nothing. A family a run's own config declares now wins over the name heuristic, and--scanwith nothing to compare says so instead of printing nothing.- Research hits carried another paper's DOI. PubMed identifiers were read
with
iter("ArticleId"), which also walks the reference list, so a paper came back with the DOI (and could come back with the PMC id) of the last paper it cites. They now come from the article's ownArticleIdList. - CrossRef research returned off-topic papers dated 2115. Sorting by publication date made the query match on any one word and put bogus future dates first ("Social Network Sites' usage among Greek students" for an interatomic-potentials query). CrossRef searches now rank by relevance, cap at today's date, and skip peer-review records.
0.2.2 - 2026-09-28
Found by installing the AgentMarkit research-assistant starter on the real
Agent37 image and asking it real questions: the install went green while the
agent's tools read an empty database. Plus the feed skill rewritten around
feed.ask, and feedback made reachable through it.
Fixed
runs.askasked "which family?" about the family it had just been told. "Compare the kdsweep runs" routed toruns.compareand then declined for want of afamilyargument; gemma4:e2b passed only the question, twice. The family now comes from the question when the argument is absent (two named at once still asks back), and caveats are joined with;so two of them no longer run together into one sentence.- On a fresh machine the agent's tools read an empty database. With no
ATTEST_DBand no legacy skill-data database, the path falls back to./hermes.db. Ingest and the refresh script run in the checkout, but Hermes startsattest-mcpfrom its own directory and strips the parent environment, so every feed answer came from an empty file beside the one ingest had filled. That was measured on the Agent37 image: 1416 items ingested, none visible to the agent.attest installnow pinsATTEST_DB=<checkout>/hermes.dbin the checkout's.envwhen nothing else fixes the path, and--checkreports the unpinned state. - A failed ingest skipped tagging. The refresh script
attest installwrites exited as soon asattest ingestdid. One refusing feed (arXiv's export API answering 406 for days) is enough to fail ingest while every other feed's items land, and those items then sat untagged. Tagging now runs regardless, and ingest's exit status is still the script's, so cron reports the failure. - The feed skill named twelve tools to a session that could call two. The
deployed feed surface serves
feed.askandfeed.toolsonly (hiding the specifics was measured: the router chosen 26/26 alone, 1/26 beside them), butattestation-feed/SKILL.mdtaughtfeed.list,feed.digest,feed.rateand nine more. In realhermes chatturns the model called them, Hermes rewrote each name tofeed.askwith the wrong arguments, every call failed validation, and the reader was told the feed was "unreachable". The skill is rewritten aroundfeed.ask(293 → 163 lines) and a ratchet intest_skill_files.pycounts hidden tools each surface skill names against its DEPLOYED session: feed 0, provenance 7, knowledge 12, symbolic 7, only down. - Feedback was unreachable.
feed.asktakes an optionalitem_idandurl:question="useful"/"not useful"with an item records the verdict, "why is this here?" explains it, anything else opens it; aurlsubscribes or previews. On a surface that serves onlyfeed.ask, "tell me which item, then I will call feed.rate" named a tool the agent could not call -- and no click had been recorded since 2026-08-22. - A digest made the agent invent papers. Its answer named only the topic,
and gemma4:e2b filled the list with titles that do not exist; named a few
titles against twelve refs, it repeated them. A digest now answers like
feed.list: item rows tagged with their topic, andrefsexactly those rows. - Reading an item answers with its text, not only its title.
Measured on 12 real hermes chat turns (gemma4:e2b, isolated HERMES_HOME,
a copy of the live database): 8/12 before. After the skill rewrite all 12
called only feed.ask with zero rewritten tool names or errors -- but the
digest turn still invented titles, so 11/12 answered correctly. After the
digest alignment fix, two re-runs of that turn each listed five real, distinct
titles.
0.2.1 - 2026-09-25
A hotfix, found by driving 39 realistic questions — nine lifted from real
Hermes transcripts that had gone wrong — through the real attest-mcp over
stdio against a copy of the live database. The first run passed 25 of 38, and
five of those passes were wrong on inspection. Every finding below is pinned by
a test at the layer it lived in, and three of those tests were mutation-checked
(each one fails with its fix reverted).
Fixed
feed.askraised a validation error on every research question. 0.2.0's paper refs carriedpaper_idand noitem_id, andRef.item_idwas required, soAnswerrejected them — after the papers had already been stored. The regression test only checked_compose's dict and never builtAnswer; the new one drives the registered tool on a real FastMCP server.- A failed research client was invisible. The headline kept only the text
before its first
;, cutting "1 client(s) failed", andcaveatwas empty while arXiv returned 406 — so PubMed papers on Alzheimer's were presented as if they were the arXiv results. Failed clients and their HTTP status now reachcaveat. - Answers that counted instead of naming.
feed.digest("16 item(s) in 2 topic(s)", zero refs though every nested item had an id and url),runs.claims_checkandruns.claims_coveragenow name what they found; contradicted and unsupported claims lead.runs.detailnames its metric values; "what are my interests?" includes the interests text. sym.verifyanswered "0" to a true identity —resultis lhs − rhs. It now answers with the verdict. It also takes an equation written with==instead of asking for both sides separately.runs.askreportedtool_used="runs.record"while returning a run listing — nothing had been written. It now says runs.record needs argumentsruns.askcannot carry.- Routing. Questions that name a subject ("latest in memory systems for
LLMs", "what is known about X") fell through to a clarifier the feed surface
cannot act on, so the agent reported nothing found; they now search.
"what feeds am I subscribed to?" was answered with suggestions, then — once
that was fixed — routed to add a feed, because
"subscribe"matched"subscribed"."sweep"matched run names (kdsweep_t4went to compare). "the top of my feeds", "when was your recent scrape?", "are you learning?" and "my main research areas" each reach the right tool. - An unknown concept is refused with the concepts the reader probably meant ("Memory System" → memory, working-memory…) instead of "call kg.concepts()", which lists 1403 names.
feed.sourcessays when feeds were last fetched, newest and stalest.
Known, not fixed here
arXiv's export API is returning 406 to this machine on every paced request
(nine at 20 s intervals). That is throttling on arXiv's side, not a malformed
request; 0.2.1 makes it visible rather than silent. sym.* calls take 5–9 s
because the sandbox's spawn child re-imports the whole server — measured, and
tracked separately.
0.2.0 - 2026-09-18
First published release. Nothing was released under 0.1.0 — the version
existed in pyproject.toml but no tag or PyPI artifact was ever cut, so
this is the repo's first public artifact despite the number.
Added
- PyPI release path (
2026-09-11):.github/workflows/release.ymlbuilds and publishes on av*tag through trusted publishing, refusing a tag that does not matchpyproject.toml; a second console script namedattestationmakesuvx attestation installthe whole install command. Version bumped to 0.2.0 for the first release. attest installhosted-models step (2026-09-11): with a non-OllamaLLM_BASE_URLthe Ollama steps skip andhosted_modelsmakes one embedding and one one-token chat request, reporting the server's own reason on failure (measured on NVIDIA NIM: 82 models listed, most chat models tried answered 410 end-of-life or 404 not-enabled, so a catalogue lookup would have passed a configuration that cannot run) and refusing an embedding model narrower thanEMBED_DIMS..env.sampleand the install guide gain the hosted tier.
Fixed
feed.research/attest researchwith--journalon CrossRef (2026-09-11): the journal was passed asquery.container-title, which CrossRef treats as a ranking hint, so a search scoped to the Journal of Chemical Physics returned Chemical Engineering Science 12 of 12 times. The client now resolves the journal name to an ISSN through/journals(title equality after normalisation, one cached request per name) and filters withissn:; an unresolved abbreviation falls back to the boost plus a client-side container-title match, which returns nothing rather than the wrong journal. PubMed's[Journal]term was already a filter and is unchanged.runs.ask(2026-09-03): comparing arms by a metric the question named silently fell back to whichever metric most arms shared instead, because_runs_askcalled_compare(family)with no metric argument at all — found via a real Hermes session asking "compare kdsweep by wer" that got a caveat computed over a different metric's spread. The same call also never surfacedwinnerin its own answer, becausewinnernames one arm rather than a collection and_RESULT_KEYS' generic "named list" path never looked for it — a caller asking "which arm won?" got the arm list back with no arm marked as the answer. Text-extracting a metric from the question alone was not enough either: gemma4:e2b paraphrased "using the wer metric, compare..." down toquestion="which arm won?"three runs straight before the tool ever saw it, soruns.askgained an explicitmetricparameter, and the provenance skill now tells an agent to pass it rather than rely on its own paraphrase carrying it.attestation-feedskill text (2026-09-03): two failures found testingfeed.askthe same way, both live-verified fixed. With no toolset restriction on the hermes session, gemma4:e2b carried ~16k prompt tokens of hermes's own built-in tools alongside attestation's two, and either invented a missing precondition ("I need to know which feeds they subscribe to first") before calling a tool it had already correctly identified, or reasoned to the right call and then printed it as literal text instead of invoking it. The skill now says explicitly thatuserandquestionare always enough and that the example call syntax is documentation, not something to type;demos/hermes/record.shpasses-t <mcp-server-name>to restrict hermes to just that server's tools, which made both failures go away in repeated live testing. Neither fix alone was reliable in this testing; both together were, twice.
Added
- Paper research (
2026-09-10, spec2026-09-10-paper-research-design.md): standing topics asresearch:feeds (attest sources add,feed.source_add), searched every ingest through arXiv, PubMed and CrossRef; thefeed.researchtool andattest researchfor ad hoc search into the library;reference_fulltextfilled byattest library fulltextand served bycite.lookupin windows; BibTeX rendered from library rows (cite.lookup,attest library export); items dedup by DOI/arXiv id across feeds; migration 009;ATTEST_RESEARCH_WEBon by default. 49 tools. Supersedes the unmerged 2026-09-04 design. demos/hermes/(2026-09-03): a fifth demo, and the only one driving a real agent rather than calling the tools directly — an asciinema recording ofhermes chatasking a real question against theattestation-provenanceskill, verified twice byte-identical after the fixes above.record.sh/README.mdfollow the same convention as the other four (script committed, output gitignored); needs a live Hermes install and Ollama, so it is not run by any test.demos/(repo root,2026-09-03): recording scripts for four short demos — the run ledger + claim checker (ledger/, asciinema), citations (claims/, asciinema),kg.*/sym.*over MCP (kg-symbolic/, asciinema, the one pair of MCP-only surfaces with no CLI/UI front end), and the HTMX web UI (feed/, Playwright, its owndemosdependency group so a plainuv syncnever installs a browser). Scripts are committed; the.cast/.gif/.webmoutput is not — same convention as the existing gitignoreddemo/. Lives at the repo root rather than underexamples/: a README one level underexamples/is swept intotest_golden_paths.py's discovery as a golden path needing the seven sections and a pinned output line, neither of which a video has.kg-symbolic/andfeed/share a seeding path (seed_kg_db.py) that runs the real ingest+tag pipeline against the flows fixture with a live chat model, because the--offlinestub's schema-shaped placeholder tags ("existing", "vocabulary", "title") produce a graph with nothing topical to show.attest runs record FAMILY --arm NAME METRIC=VALUE...(2026-09-01) writes the results/config JSON+YAML pair,corpora.toml, andmetric_direction.tomlentries a run needs deterministically, refusing before writing anything if a target already exists (--forceto overwrite) or if a metric's ranking direction is undeclared (the same refusal sentenceruns.compareprints) — replacing a five-step manual procedure whose declaration step small local models followed 0/15 of the time, against ≥0.91 on every file-shape step.--dry-runprints the{"files": {relpath: content}}manifest the command's own acceptance eval (evals/run_record_eval.py --command, 11/11) scores against the real ledger reader;--scanfoldsruns scan+runs compareinto the same invocation.src/attestation/record.pyis pureplan()/undeclared()plus onewrite()I/O function and amerge_toml_table()helper, with nosqlite3orattestation.llmimport.- The bundled skill split five ways (
2026-09-01, landed from an Aug-30 worktree):attestation-setupplus one skill per agent surface replace the single 39.5 KBresearch-provenancemonolith;attest installnow syncs all five into~/.hermes/skills/and every profile's skills tree, respects aSKILL.md.<anything>disable rename, and retires an installed monolith by renaming itsSKILL.md, never deleting. - Twelve golden-path worked examples under
examples/, each runnable from a clean clone with a fixed-shape README and arun.sh(a3387d5..e5e511b, framework ineae9e49): the four retrofitted paths (workspace/,flows/,prompt-evals/,agents/,e9f168e,45718bb) plus real third-party integrations for MLflow (836c9fc,4761a9f), W&B (dc496da), Sacred (fdaa22f), DVC (205e1c3), Hydra (620f300), TensorFlow/Keras (b3f1a2f), citations (492af29), and bring-your-own model server (871436b). The catalogue and its ordering are enforced bytests/test_golden_paths.py, added in the same sweep. - Five tracker read conventions in
ledger_adapters/generic.py, each reading a real on-disk layout with no dependency on the tracker's own package: Sacred'sFileStorageObserver(be9ca47), DVC'sdvc.yaml/dvc.lock(ec29ece), and Hydra's--multirunsweep directories (4b72989) join the existing MLflow and W&B readers. - Task corpora and model-free scorers for the reaction and explanation
prompts (
e228d0e,d4f5741), matching the treatment tagging already had — each gets a labelled corpus, a scorer independent of any model, and one public renderer that both the library and the eval script call (d6dac2e,cb1ca3a,d3a04b0). - A stub OpenAI-compatible server (
365339a) and a persona-reaction evaluation harness (ae32960) so the example flows and CI run fully offline, withRESULTS.mdwritten only by a live run (05fea1c). - The package states its own surface:
attestation/__init__.pygained a docstring,__version__(read from installed metadata), and an__all__naming the modules meant to be imported; apy.typedmarker now ships in the wheel;[project.urls]gained Homepage, Repository, Issues, and Changelog (7bc20cf). tests/test_docstring_ratchet.py: every public def undersrc/attestation/**now carries a docstring (103 were missing; ratchet baseline is 0 and only goes down), andcli.py'scmd_*handlers share one source of truth with their argparsehelp=text (7bc20cf).- This file and
CONTRIBUTING.md(this change).
Changed
generic.py's stem-family grouping now handles a bare split-token stem (lr_0.001) by falling back to the token's own name as the family, instead of returning no family at all — found byexamples/tensorflow/'s real four-arm sweep (ca08646).- The DVC comment stripper is now quote-aware, since a
#inside a quoted YAML scalar is not a comment (83783fd); an earlier DVC review round also fixed a metrics-directory collision and a trailing-comment misattribution, each landing with its own test (0735cf1). - The attribution-and-machine-path guard, originally scoped to
examples/flows/, now covers all ofexamples/**, scans non-text files (e.g. TensorBoard's binary.v2event files) as raw bytes, and skips the ambient-$USERcheck on CI and for generic account names (runner,root, …) so it stops colliding with ordinary English words like "runner" in prose (e5e511b, and the CI-username finding recorded in2026-08-28-golden-paths-design.md's Deviations section). - The repo README's "Try it in 60 seconds" area became a "Golden paths"
section pointing at the
examples/README.mdcatalogue instead of describing each flow inline a second time; the quickstart block itself is unchanged (e5e511b). - CI: a wheel-smoke step now asserts
py.typedships in the built wheel (7bc20cf); an earlier fix made the local ruff-format hook agree with CI's Markdown-fence formatting, after CI'sruff format --check .failed on design specs no local gate had ever touched (ccf878f).
Fixed
attest claimsandcoveragescanned to zero files in any checkout under a dotted directory (a git worktree in.claude/worktrees/): hidden-directory filtering now judges paths relative to the scanned root, not the absolute path.attest claims(the CLI) never ran the citation lint that the MCP tools (cite.check,runs.claims_check) already ran — found byexamples/citations/exercising both paths against the same draft, and fixed to match (4fb6007, documented incheck_citations.py's docstring byd323b52).- Sacred, DVC, and Hydra fixture generators were unpinned from the library
version they were verified against; all three now pin and refuse to run
under a different installed version, matching the convention W&B's and
TensorFlow's generators already followed (
96c2386). - A macOS CI run staged "no
python3anywhere," which does not reflect a real macOS box (Python 3 ships at/usr/local/binthere); the loud- failure test's assumption was corrected (820006e). A separate macOS failure came from a ruffFAILEDline that ran to 123 columns — long enough that only the local terminal's tail hid it (22e991a). - Two earlier CI-only failures: symbolic calls returned an rlimit error
because the daemon refresh script never ran on macOS (
a3ed03c), and the first green CI run needed a stubbed daemon test plus a Python build that can load thesqlite-vecextension (d4ea750).