tyamka
Zero-token memory for your Claude Code agent — plus a local-first knowledge base it can read.
tyamka gives your coding agent a memory that costs no LLM tokens: it records why your code changed, then feeds the relevant history back into your next prompt automatically — all on local embeddings, in one SQLite file you own. It's also a full knowledge-base compiler: research topics with parallel agents, compile cited, confidence-scored articles, and query them with hybrid retrieval.
Example — what the why-guardrail hands your agent before it edits a file, on zero LLM calls:
$ tyamka why tyamka/ops/recall.py
● tyamka/ops/recall.py — 3 recorded decisions (newest first)
2d ago · refactor
Embed the prompt once, then gate candidates against their stored vectors —
recall must stay under the 15s UserPromptSubmit hook timeout.
23d ago · decision
Gate at cosine 0.42 — measured, not guessed: on paraphrase-multilingual-
MiniLM-L12-v2 it keeps 13/18 relevant prompts and admits 2/24 noise ones.
zero LLM calls · pure SQL + one local embedding
- Zero tokens by default — capture, recall, and context injection run on local embeddings and make no LLM calls. You spend tokens only when you explicitly research, compile, or summarize.
- Local-first — one SQLite file (WAL, FTS5 full-text +
sqlite-vecvectors). No server, no cloud state, no lock-in — your~/wikiis the whole system of record. - Provenance + injection-aware — every finding and citation traces back to an immutable raw source, and all ingested text is sealed as untrusted
<source_data>before it ever reaches a model (on the way out to an agent's context, too). - Two thin surfaces, one core — a
richTyper CLI and afastmcpMCP server are both thin wrappers over one shared service layer. Nothing is implemented twice.
/plugin marketplace add tyamka/tyamka
/plugin install tyamka@tyamka
/tyamka:init
Full walkthrough → docs/GUIDE-en.md · Architecture → docs/ARCHITECTURE.md · How it works → #how-it-works · Agent memory in depth → #agent-memory-that-costs-no-tokens
Install as a Claude Code plugin
The repository doubles as a Claude Code plugin — /tyamka:* slash commands plus MCP tools, running on your Claude subscription (no API credits) or an Anthropic API key.
/plugin marketplace add tyamka/tyamka
/plugin install tyamka@tyamka
At install you choose the LLM backend (subscription or api). Install the tyamka CLI yourself — no hook does it for you (a few minutes on first run, including a one-off local embedding-model download), and re-run the same command after pulling an update:
uv tool install --force --reinstall --no-cache <this repository's directory>
Until it is on PATH every hook no-ops behind its command -v tyamka guard. Then:
/tyamka:init # create a knowledge base for this project
/tyamka:research "..." # gather knowledge (or /tyamka:ingest <url|pdf|file>)
/tyamka:compile # synthesize cited articles
/tyamka:query "..." # cited answers
The Viewer UI starts itself in the background on session start — a local, read-only web view
over every wiki on your machine. Open http://127.0.0.1:8080 once a session has begun (the very
first run builds it once — see Viewer UI). Needs java on your PATH (java
npmfor that first build); it silently does nothing if they're absent.
Requires uv plus either a logged-in claude CLI (subscription) or an ANTHROPIC_API_KEY (API backend). Full setup, commands, and caveats: docs/PLUGIN.md. An end-to-end walkthrough — the canonical loop, worked scenarios (including a wiki inside a code repo), and every command with its token cost: docs/GUIDE-en.md.
The rest of this README covers running tyamka from source as a standalone CLI + MCP server.
Releasing
Tagging is the whole release. .github/workflows/release.yml fires on a v* tag, runs the full
gate (pytest, ruff, mypy --strict) plus a check that the tag and the package version agree,
verifies the built wheel actually carries its runtime data files, publishes to PyPI through
Trusted Publishing — no API token exists in the repository or in any secret — and opens a
GitHub Release whose body is that version's CHANGELOG.md section.
# bump the version in all four places, update CHANGELOG.md, then:
git tag -a v0.6.1 -m "0.6.1 — ..." && git push origin v0.6.1
A PyPI version is immutable, which is why the gate runs before the upload rather than after.
Requirements
- Python 3.13+
- uv for dependency management
- One LLM backend: either an Anthropic API key (
[llm] backend = "api") or a logged-inclaudeCLI (backend = "subscription"— no API credits). See Choosing an LLM backend. - A Voyage AI key is optional (hosted embeddings; the default runs a local embedding model with no key). Capture, recall, and
--extractneed no LLM backend at all — only the embedder.
Install
uv sync
This installs everything, including a local embedding model (sentence-transformers) and a cross-encoder reranker that are downloaded on first use.
Migrating from wikiforge
tyamka was called wikiforge through 0.5.0. Nothing about your data changes — the rename touches names on disk only, and no wiki is rewritten:
uv tool install --force --reinstall --no-cache <this repository's directory>
tyamka migrate # .wikiforge/ -> .tyamka/, and ~/.config/wikiforge -> ~/.config/tyamka
Then update any hooks.json you wired by hand: every wiki … becomes tyamka …, and the guard
becomes command -v tyamka. The plugin's hooks/hooks.json.example already reads that way.
tyamka migrate is free, offline, and idempotent — it never opens the database, so it works on a
wiki this build could not otherwise read. If both .wikiforge/ and .tyamka/ exist it refuses
and moves nothing: two directories can hold two different dev logs, and picking a winner silently
would lose one.
Until 0.7.0 you can skip it. The old names still resolve, read-only and one release only:
.wikiforge/ is found when .tyamka/ is absent, $WIKIFORGE_HOME and $WIKIFORGE_BACKEND are
honoured after their TYAMKA_* spellings, ~/.config/wikiforge/peers.toml is read when the new
registry does not exist (registering any peer writes the new one), and the Viewer scans for both
layouts. The wiki command also still works — it prints one deprecation line on stderr and
delegates to tyamka.
Configure secrets (environment only)
Secrets are never stored in the config file — they are read from the environment:
export ANTHROPIC_API_KEY=sk-ant-... # required for [llm] backend = "api" only
export VOYAGE_API_KEY=pa-... # optional — enables hosted embeddings
export TYAMKA_HOME=~/wiki # optional — default wiki location (else ~/wiki)
With backend = "subscription" no key is needed — tyamka shells out to your logged-in claude CLI instead.
First run
# 1. Create a wiki (config.toml, SQLite DB, topics/ dir)
uv run tyamka init "My Wiki" --home ~/wiki
# 2a. Grow it autonomously — research agents fan out, gather, and normalize findings
uv run tyamka research "Rust async runtimes" --new-topic
# 2b. …or add sources by hand (URL / PDF / text file)
uv run tyamka ingest https://tokio.rs/blog/2020-04-preemption
# 2c. …or attach an internal/private source to a topic — compiles with NO web search
uv run tyamka ingest ./docs/internal-auth-design.md --topic "our-auth-design" --new-topic
# (already ingested? bind it later: uv run tyamka attach <source-id> our-auth-design)
# 3. Compile gathered evidence into cited, confidence-scored articles
uv run tyamka compile
# 4. Ask questions over the compiled knowledge (answers cite their sources)
uv run tyamka query "How does cooperative scheduling work in async Rust?"
# 5. Explore
uv run tyamka related rust-async-runtimes
uv run tyamka stats
uv run tyamka export site --out ./site # static HTML you can open in a browser
Which wiki a command talks to. Every command accepts
--home <dir>and every command resolves it the same way, in this order:--home→ the main repository's.tyamka/if it exists → the current directory's.tyamka/→$TYAMKA_HOME→~/wiki. So inside a project with a.tyamka/,tyamka compileandtyamka queryread exactly the wiki thatcapture,recallandwhywrite to; outside one, everything lands in~/wikias before. Only--homeoverrides a project wiki —TYAMKA_HOMEsets your default, not a project's.
Project identity (project.toml is committed; wiki.db is not)
tyamka init mints a project_id (a UUID) and records the normalized git remote as
remote_key, storing both in the database and in <home>/project.toml:
.tyamka/*
!.tyamka/project.toml
Those two lines are the point. Before 0.6 a wiki's identity was its path on disk, which makes two
clones of one repository — a laptop and a desktop — two unrelated projects. The database beside the
file is a local cache and stays ignored; the identity is what has to travel with the code, so a
fresh clone adopts the project it was cloned into rather than minting a second one. Commit
project.toml; ignoring .tyamka/ wholesale silently re-creates the problem.
remote_key is derived, not authoritative — git@github.com:you/x.git and
https://github.com/you/x both normalize to github.com/you/x, which is what lets two clones be
recognised as one project even if each minted its own project_id first. A repository with no
origin (~/wiki, a scratch repo) simply has no remote_key; that is normal, not an error. It is
re-read on tyamka init and tyamka stats, so a repository that moves to a new host or org catches
up without a special command. tyamka stats prints both, as does the MCP get_stats tool.
Exporting the wiki (tyamka sync)
tyamka sync push --dry-run --out push.ndjson writes every row this wiki has not yet sent as
NDJSON — one kind-tagged record per line. There is no server: without --dry-run the command
exits 2 and says so. What exists today is the payload and its schema.
tyamka sync push --dry-run --out /tmp/push.ndjson # the delta since the last watermark
tyamka sync push --dry-run --out /tmp/all.ndjson --since-zero # everything — a portable backup
tyamka sync schema # the JSON Schema a server must accept
Nothing rowid-shaped is in the file. Rows are addressed by what identifies them on any machine —
content_hash, topics.slug, (topic_slug, version) — because a SQLite rowid is meaningless
anywhere else. The exceptions are named local_rowid / local_session_rowid and belong to the
ledger tables, which have no natural key at all; those are only ever meaningful paired with the
device in the header.
--dry-run never advances sync_state. Nothing has acknowledged these rows, and a watermark moved
past rows no server received is data silently never sent. Dev-event bodies are redacted a second
time on the way out: capture redacts on the way in, and this is the moment text leaves the machine.
How it works
ingest / research → raw_sources (immutable) → chunks (FTS5 + vector index)
│
▼
compile → articles (versioned, cited, confidence-scored)
│ │
▼ ▼
topic_links (graph) query (hybrid retrieval + RAG)
- Ingestion canonicalizes URLs (strip tracking params, normalize host/scheme), extracts clean text (
trafilaturafor HTML,pymupdffor PDF), and dedups bysha256content hash. Re-ingesting the same content updates provenance, never the immutable text. - Research fans out persona agents (academic, technical, applied, news, contrarian, …) in waves using
asyncio.TaskGroup, with a per-session USD budget and resumability. Each agent web-searches, stores its finding as an immutable source, and normalizes it into the schema. - Attaching binds an ingested source directly to a topic (
tyamka ingest --topic/tyamka attach) so it compiles into that topic's article with no web search and no LLM spend — the bridge for internal or private material (a project's own dev log, an internal design doc) that public research would never surface. Research findings and directly-attached sources feed the same compiler; a topic's article can rest on either or both. - Compilation synthesizes a topic's evidence — researched findings and directly-attached sources alike — into a Markdown article with inline citations, detects conflicts between sources, and computes a confidence score in code (source count, diversity, recency, evidence strength, minus a conflict penalty). Compilation is incremental — an unchanged content digest is skipped unless
--full. It makes no web-search calls: it synthesizes only over the sources already tied to the topic. - Retrieval merges FTS5 BM25 and
sqlite-vecKNN rankings via Reciprocal Rank Fusion.--scope(all/articles/devlog) picks what's searched — everything, by default, at any depth.--depth(quick/standard/deep) picks ranking effort only;deepadds a cross-encoder rerank. - Cost for every LLM/embedding call is priced from the config and logged;
tyamka statstotals it.
Architecture
Two thin surfaces (a rich Typer CLI and a fastmcp MCP server) and the Claude Code hooks all call one shared service layer — nothing is implemented twice. Everything persists to a single local SQLite file; the LLM and embedding providers sit behind Protocols, so the whole test suite runs with no network and no keys.
flowchart TB
CLI["wiki CLI · Typer"] --> SVC
MCP["MCP server · fastmcp"] --> SVC
HOOKS["Claude Code hooks<br/>capture · recall · why-guardrail"] --> SVC
SVC["Service core — one shared layer<br/>(services.py)"] --> STORE["SQLite · WAL<br/>FTS5 + sqlite-vec"]
SVC --> LLM["LLM providers · Protocol<br/>Anthropic API / claude -p<br/>+ Governed budget"]
SVC --> EMB["Local embeddings<br/>paraphrase-multilingual-MiniLM-L12-v2"]
- Two paths, one core. The paid path (
research → compile → query) is always explicit; the free path (capture, recall, the why-guardrail, MCP excerpts) runs on local embeddings and makes zero LLM calls. - Immutable provenance. Raw sources are content-addressed and never mutated; every citation is audited against them.
- Injection defense is uniform. A single
seal_source_datachokepoint neutralizes<source_data>delimiters in every untrusted or model-generated string — on the way in and out.
Full design doc → docs/ARCHITECTURE.md.
Agent memory that costs no tokens
tyamka remembers why your code got to be the way it is — and hands that memory back to your coding agent automatically. The whole loop runs on local embeddings and makes zero LLM calls.
Off by default. None of the rows below fire until you opt in: copy
hooks/hooks.json.exampletohooks/hooks.json(or paste the entries into a project's.claude/settings.json) and flip[capture] auto,[recall] enabledand[why] guardrailtotruein that wiki'sconfig.toml. Each surface spawns one CLI process per prompt, stop or edit, and each loads the embedding runtime — on a busy session that is real RAM, so the wiring is a decision you make, not a default you inherit. Everything works manually without it:tyamka capture --note,tyamka why <path>,tyamka query.
| When | What happens | LLM cost |
|---|---|---|
| A task edits files | Stop hook records a dev event: your request (the why), changed files + git diff --stat (the what), an inferred type, the time |
none |
| Session starts | tyamka maintain --hook runs its free jobs (backfill dev-log vectors, keep the file index current, purge expired hook dedup rows, check registered peers) then its paid jobs while the budget allows (digests, consolidation — see Maintenance budget) |
bounded by [maintain] (default ≤8 calls / $0.50 per rolling 24h) |
| You type a prompt | UserPromptSubmit hook injects the most relevant wiki + dev-log excerpts (multilingual, recency-weighted, deduped within the session) into the agent's context, so it skips re-exploring what's already known |
none |
| The agent asks the wiki | MCP search_knowledge returns cited excerpts for the agent to synthesize in its own (already-paid-for) context |
none |
You run research / compile / --digests |
the only paths that spend tokens — always explicit | you choose |
That last column is the whole design: on a Claude subscription every LLM call carries ~22K tokens of Claude Code harness overhead, so the cheapest call is the one never made.
It captures uncommitted work, so you never have to commit for the wiki to remember.
- Automatic: fires when a task changed files. No action needed.
- Subagent work is captured too. A
SubagentStophook records what each subagent changed, keyed by its own session so parent and child never double-capture. Without it, work delegated to subagents is invisible to the dev log — which, in a subagent-driven workflow, is most of the work. Turn it off with[capture] subagents = false. - Decisions that changed no file are captured before they're lost. A
PreCompacthook fires while the pre-compaction transcript is still intact and sweeps up the turns nothing else records — the design discussion, the investigation, the rejected alternative. (Stoponly records turns that edited files, so those conversations previously vanished at compaction.)[capture] precompact, capped byprecompact_max_chars. - Research notes: for investigations that changed no files, run
/tyamka:wiki-note "what you found and why it matters". - Where it lands: the main repository's
.tyamka/when you're in a git repo — resolved viagit rev-parse --git-common-dir, so a subagent running in its own worktree still writes to the one project wiki instead of forking memory per worktree — else the project-local.tyamka/, else your default wiki. Runtyamka initthere first. Every other command resolves the same way, sowiki compilein that project compiles these events rather than a different wiki's. - Re-reading a transcript is safe. Each event is stamped with its turn's timestamp, not the time the hook ran, so an event's content hash is stable — if a watermark is ever lost (a rotated or replaced transcript) the replay dedups against what is already stored instead of duplicating it.
- Each event records where it happened: the branch, the short HEAD SHA, and whether it was a worktree. Capture still records uncommitted work, so these say where a decision was made — they don't tie the event to a commit.
- Summarization is zero-LLM by default.
[capture] summarizeis"off"|"sync"|"deferred"(default"deferred"): short requests (<= summarize_min_chars, default 200) become their own summary verbatim, no LLM call, ever; longer ones are stored with no summary and marked digest-pending."sync"is the old behavior — one cheap-tier call per event, at capture time."off"never summarizes. - Clearing the backlog:
tyamka capture --flushbackfills any dev-log chunks missing vectors (free, no LLM), and with[capture] auto_digest_batches(default 1,0disables) also drains up to that many digest batches — one cheap-tier call per batch of up to 25 events. Add--digeststo force a full manual drain (unbounded batches). AtSessionStartthis backfill now runs as the freevectorsjob insidetyamka maintain --hook(see Maintenance budget) rather than as its own hook line — same work, now probed first and accounted. - Consolidation:
tyamka consolidaterolls dev events older than[consolidate] min_age_days(default 14) into a versioned development-log article, grouped byperiod(week|month) — one cheap-tier call per period. Consolidated events drop out of recall (the rollup represents them) but stay searchable via--scope devlog. Each consolidated event is also routed into its most relevant compiled topic — a local embedding match above[consolidate] route_min_similarity, zero LLM — and attached there, so that subject article cites the internal dev event on the nexttyamka compile. The dev log stops being write-only history and compounds into the knowledge base. Still gated by[consolidate] auto(default off); atSessionStartthat check now runs as theconsolidatejob insidetyamka maintain --hookinstead of its own--if-autohook line — a run with nothing eligible is a free no-op either way. - Read it back:
tyamka query "why did we change the retriever?"— dev events are searched by default (--scope all, the default) at any--depth; scope, not depth, controls whether the dev log is included. Use--scope devlogto search only dev events,--scope articlesfor only compiled articles.--depth deepnow only affects ranking (adds a cross-encoder rerank) — it no longer changes what's visible. - Proactive recall: a
UserPromptSubmithook (tyamka recall --hook, zero LLM, 15s timeout) injects the most relevant wiki/dev-log excerpts into the session before the agent even starts, so it can skip re-exploring what the wiki already knows. It embeds your prompt once and gates candidates against their stored vectors (no re-embedding), so the retrieval work is ~20 ms; a fresh project with no chunks exits before loading the model at all. Configure it under[recall]inconfig.toml:enabled(defaulttrue),max_excerpts(default 3),max_chars(default 600),min_similarity(default 0.42 — measured on the default multilingual embedder against a live wiki: 18 relevant / 12 adjacent-technical / 12 off-topic uk+en prompts, where 0.42 keeps 13/18 relevant and admits 2/24 noise; adjacent-technical prompts, not off-topic ones, are what set it),dedup(defaulttrue— never re-inject a chunk already shown this session),devlog_half_life_days(default 14 — fresher dev events outrank staler ones at equal relevance;0disables), androuting_hint(defaultfalse— append a zero-LLM task-type hint for an orchestrator's model-routing policy; a hook cannot switch the active session's model, so it is a hint only), andannotate(defaulttrue— prefix each excerpt with its epistemic status, e.g.(article · confidence 0.61 · researched 42d ago · HIGH volatility)or(dev event · 3d ago · bugfix), so the agent knows how far to trust it; missing fields are omitted, never guessed). - Privacy / control: the raw request is stored (best-effort secret redaction). Turn capture
off with
[capture] auto = false, or raw-only withsummarize = "off", inconfig.toml.
Why is this code the way it is?
git blame tells you who changed a line and when. tyamka answers why — because the dev log
already records the reasoning behind every change, indexed by the files it touched.
tyamka why tyamka/ops/recall.py # decision history for a file, newest first
tyamka why src/api/routes.py --limit 10
Matching accepts an absolute path or any /-anchored suffix (recall.py, ops/recall.py, the full
path). A path:line argument is accepted and the line part is honestly ignored — attribution is
file-level until hunk capture lands. Agents get the same data through the MCP tool why_file, sealed
as untrusted data to synthesize from. Both paths are zero-LLM — pure SQL, no model is ever loaded.
The guardrail. A PreToolUse hook (tyamka why --hook, default off — [why] guardrail) runs before the agent edits
a file and, when that file carries decision history, hands the agent the past reasoning — so it doesn't
silently undo a decision it can't remember. It only informs, never blocks: it always returns an
allow decision and delivers the note as additionalContext (plain hook stdout would reach only Claude
Code's debug log, never the model — verified against Claude Code 2.1.207). Tuning lives under [why]:
guardrail (default true), guardrail_exclude_types (default ["chore", "docs"] — everything else
warns, so routine edits stay quiet while real decisions surface), and guardrail_max_events (default 2).
The older guardrail_types whitelist is still read for one release; when both are set the exclude-list
wins. Each file warns at most once per session, and the whole lookup is pure SQL.
Changelog and impact: the dev log as two more views
tyamka changelog and tyamka impact read the same file-indexed dev log tyamka why does — a changelog
for a range of commits, and the blast radius of a source, file, or topic.
tyamka changelog # dev-log changelog for upstream/main..HEAD, newest first
tyamka changelog v1.2.0..v1.3.0 # any git range: A..B, A...B, or a single ref
tyamka changelog --prose # one cheap LLM call rewrites it as release notes / a PR body
tyamka impact tyamka/ops/why.py # what rests on a file
tyamka impact development-log # ...or a topic slug
tyamka impact https://example.com/post # ...or a source (URL, content hash, or id)
tyamka changelog <range> selects the dev events behind the range's commits — matched by changed file,
and by a file-less time window for the design discussions the PreCompact hook otherwise leaves
undiscoverable — then renders them grouped by type, newest first. The coverage footer is not
decoration. It states exactly how many of the range's changed files have a recorded decision, and how
many entries were matched by file versus by time window — read it as an honest measure of how much of
the range the dev log actually explains, not as a claim that the changelog is complete. Zero LLM by
default; --prose spends one cheap-tier call turning the same rendered data into prose.
tyamka impact <target> reports what rests on a source (live citing claims and research findings), a
file (the decisions that touched it, plus files it has historically changed alongside), or a topic (the
sources its current article cites, and which other topics share them). "Changed together with" is
historical correlation, not a causal rule — two files edited in the same turns are a coupling hint
worth checking, not a rule that one must change whenever the other does. Both commands are zero-LLM
(pure SQL over the same dev log and citation graph tyamka why and tyamka audit already use) and
MCP-exposed as build_changelog / impact_report, sealed as untrusted data for the calling agent to
synthesize.
Federated memory
tyamka peers extends the read surfaces above — why, changelog, impact, recall, and query --extract / MCP search_knowledge — across every other wiki you register, read-only. A decision
captured in one project's dev log becomes visible from another's.
tyamka peers add ~/other-project/.tyamka # register another wiki as a read-only peer
tyamka peers list # reachability, model, compatibility, and the fix for anything not ok
tyamka peers rm <alias> # the per-peer off switch
-
The registry is machine-global, not per-project. It lives at
$XDG_CONFIG_HOME/tyamka/peers.toml(falling back to~/.config/tyamka/peers.toml) — outside every wiki and every repo, because a peer entry is an absolute machine path and a project'sconfig.tomlcan travel with its repository. Each wiki still opts in to reading the registry independently ([federation] enabled, defaulttrue); disabling it, or removing every peer, restores exactly today's single-wiki behavior. -
Read-only is SQLite's guarantee, not this codebase's. A peer's
wiki.dbis opened withmode=ro— every write, including a rawconn.execute, is refused at the driver level. Federation can read another wiki; it cannot write to it, migrate it, or upgrade its schema. -
Three compatibility states, read from the peer's stamped
wiki_meta.embedding_model— never from itsconfig.toml, which only says what the model would be on the next run, not what the stored vectors were actually built with:verdict meaning recall/query --extract/search_knowledgewhy/changelog/impactokpeer's stamped model matches yours contributes contributes mismatcha different model is stamped skipped entirely contributes unknownno stamp (no wiki_metatable, or a row missing theembedding_modelkey)skipped entirely contributes Vector federation needs a matching stamped model.
unknownis deliberately not treated as "probably fine" — feeding unverified vectors into a measured similarity gate isn't a risk worth taking on a guess.tyamka reindex --embeddings --home <peer>is both the diagnosis and the fix, run by that peer's own owner (repairing another wiki's index from here would itself be a cross-wiki write).
Two limitations, stated plainly rather than buried:
- An unstamped or mismatched peer contributes nothing to vector paths (
recall,query --extract,search_knowledge) until its owner reindexes it. - A peer without a
dev_event_filestable contributes nothing towhy,changelog, orimpact. Those commands index by file path; a peer that has never captured a dev event, or predates that table, is skipped — not errored.
Every federated result carries its origin: · from <alias> in recall excerpts, [alias] in why
output, a per-origin line in changelog's coverage footer — a cross-project answer is never presented
as if it were local history. Recall's excerpt cap is applied after the merge across wikis, so
federation changes which excerpts arrive, never how many. An unreachable, locked, or slow peer is
dropped within [federation] peer_timeout_ms (default 500 ms) and never blocks the caller.
Maintenance budget
tyamka maintain is the one accounted entry point for automatic upkeep — it replaced two separate
SessionStart hook lines (capture --flush, consolidate --if-auto) with a single job queue, each
job gated by its own cheap probe so nothing pays for work that doesn't exist:
| # | job | cost | does |
|---|---|---|---|
| 1 | vectors |
free | backfill dev-log chunk vectors missing an embedding |
| 2 | paths |
free | build/backfill the file→event index (dev_event_files) |
| 3 | peers |
free | check each registered peer's reachability and compatibility — reports the fix, repairs nothing (a cross-wiki write) |
| 4 | digests |
paid | batch-summarize pending dev events, one cheap call per 25 |
| 5 | consolidate |
paid | roll old events into the versioned development-log article |
tyamka maintain --dry-run # the plan: is there work, is it free or paid, would the quota allow it
tyamka maintain # run it
tyamka maintain --force # ignore the quota for this run (still recorded; still counts against later runs)
tyamka maintain --hook # SessionStart mode: silent, always exits 0 — what the hook actually runs
- The ledger is derived, not a new table.
llm_callsalready recordspurpose, both token counts,cost_usdand a timestamp; maintenance spend in the current rolling window ([maintain] window_hours, default 24) isSUM(cost_usd) WHERE purpose LIKE 'maintain:%' AND ts >= window_start. One source of truth — nothing to keep in sync, nothing to drift. - The
maintain:purpose prefix is what makes that query complete.GovernedProviderwraps the real LLM provider for the duration of a run and rewrites every call'spurposetomaintain:{purpose}before forwarding it, so a job added later is counted automatically with no per-job plumbing to remember. (Without it,digestswould record plainpurpose="capture"— indistinguishable from interactive, non-budgeted capture.) - Overshoot is bounded by exactly one call. A call's cost is only known after it returns, so
enforcement is pre-call: the ledger is re-read before every call, and a call is refused once
max_calls_24h(default 8) ormax_usd_24h(default $0.50) is already reached. Worst case, the one call already in flight completes past the cap — fractions of a cent for a cheap-tier call — stated as a bound, not engineered away. - The governor spends nothing the user hadn't already opted into.
digestsstill honors[capture] auto_digest_batches;consolidatestill honors[consolidate] auto(default off). What changes is that the spend is now accounted, capped across sessions, inspectable with--dry-run, and reached through one hook line instead of two.
Configure under [maintain]: enabled, window_hours, max_calls_24h, max_usd_24h, and jobs
(the ordered list to run — an unrecognized name is ignored, so a config written for a later version
still runs here).
The free hygiene job purges the two per-session dedup tables once their rows are past retention
(why_log after 7 days, capture_watermark after 30). It is a maintenance job specifically so the
PreToolUse guardrail doesn't do it: that hook runs before every Edit/Write, and week-old
retention has no business costing write transactions on that path.
Command reference
| Command | What it does |
|---|---|
tyamka init <name> |
Create a wiki (config, DB, topics/). |
tyamka ingest <url|path> |
Ingest a URL, PDF, or text file into the indexed knowledge base. --topic <slug> also attaches it to a topic (add --new-topic to create the topic) so it compiles into that article — no web search, no LLM spend. |
tyamka attach <source> <topic> |
Bind an already-ingested source (numeric id, #id, content hash, or URL) to a topic so it compiles into the article. --new-topic creates the topic. Zero LLM. |
tyamka research "<topic>" |
Research a topic with persona agents. --mode standard|deep|max, --new-topic, --budget <usd>, --resume <session-id>. |
tyamka thesis "<claim>" |
Evaluate a claim with FOR/AGAINST agents → a cited verdict. --mode, --budget. |
tyamka compile |
Compile active topics into cited articles. --full recompiles everything. |
tyamka query "<question>" |
Answer a question over compiled knowledge, citing sources. --depth quick|standard|deep (ranking effort only), --scope all|articles|devlog (default all — what's searched), --extract (zero-LLM: print excerpts instead of a synthesized answer). |
tyamka related <topic> |
List knowledge-graph neighbours of a topic. |
tyamka generate <kind> <topic> |
Generate a derived document (report, slides-outline, summary, study-guide, timeline, glossary, comparison). --out <file>. |
tyamka export <target> |
Export to obsidian (vault + frontmatter), site (static HTML + graph page), or json (structured dump). --out <dir>. |
tyamka lint |
Audit for broken wikilinks, orphans, missing citations, staleness. --fix applies safe repairs. |
tyamka audit <topic> |
Re-verify a topic's citation quotes still match their immutable raw sources. |
tyamka refresh |
List topics whose freshness window has lapsed; --run re-researches them. |
tyamka collect <collection> <url|path> |
Catalogue a source into a named collection (recorded, not search-indexed). |
tyamka dataset add <name> <path> |
Track an on-disk dataset (name, path, size). |
tyamka archive <topic> |
Archive a topic (excluded from default query/retrieval). |
tyamka feedback <target> <approve|reject|correct> [note] |
Record a verdict against an article (article:<id>) or finding (finding:<id>). |
tyamka capture |
Record a dev event. --hook (reads Claude Code Stop JSON on stdin), --note "<text>" + --type, or --flush to backfill dev-log vectors (free) with optional --digests to batch-summarize. |
tyamka recall --hook |
Read a Claude Code UserPromptSubmit payload on stdin and print relevant wiki excerpts for the agent's context. Zero LLM. Always exits 0. |
tyamka why <path> |
Show WHY a file is the way it is — the dev events that touched it, newest first. --limit N; --hook reads a PreToolUse payload and emits the guardrail warning. Zero LLM. |
tyamka changelog [range] |
Why-annotated changelog for a git range (default: upstream/main..HEAD; also accepts A..B, A...B, or a single ref). --limit N, --exclude-types a,b. Zero LLM; --prose rewrites it as release notes / a PR body (one cheap LLM call). |
tyamka impact <target> |
Blast radius of a source (URL/hash/id), a file path, or a topic slug — what claims, decisions, or co-changed files rest on it. --limit N, --as source|file|topic to force the reading. Zero LLM. |
tyamka peers add|rm|list |
Manage federated read-only peer wikis: register (add <path> [--alias NAME]), remove (rm <alias>), or list all with reachability, model, and compatibility (list). |
tyamka consolidate |
Roll dev events older than [consolidate] min_age_days into a versioned development-log article, and route each event into its most similar compiled topic (zero-LLM embedding match) so it becomes a citation there. --if-auto runs only when [consolidate] auto = true. |
tyamka maintain |
Run automatic maintenance (vectors, file index, peer check, digests, consolidate) within its budget. --dry-run shows the plan and spends nothing, --force ignores the quota once, --hook is the silent SessionStart mode. |
tyamka reindex --embeddings |
Rebuild every chunk vector with the active embedding model (local, zero LLM) — required after changing local_model. |
tyamka stats |
Project identity (project_id, remote_key), wiki size + LLM spend. --since <YYYY-MM-DD> adds a spend window. |
tyamka context |
Print a recent-activity digest for pasting into an agent's context. |
tyamka sync push --dry-run --out <f.ndjson> |
Serialize unsent rows as NDJSON (natural keys only, zero LLM, no network). --since-zero exports everything — a portable backup. Never advances sync_state. Without --dry-run it exits 2: there is no server yet. |
tyamka sync schema |
Print the JSON Schema one payload line must satisfy. --out <file> writes it. |
tyamka serve-mcp |
Serve the wiki over MCP (stdio transport). |
tyamka migrate |
Rename a pre-0.6 .wikiforge/ wiki (and the peer registry) to .tyamka/. Free, offline, idempotent; refuses if both exist. |
tyamka version |
Print the installed tyamka version. |
Configuration (<home>/config.toml)
tyamka init writes a documented default. Highlights:
wiki_name = "My Wiki"
[models] # model routing by tier
cheap = "claude-haiku-4-5"
flagship = "claude-sonnet-5"
reasoning = "claude-opus-4-8" # optional 3rd tier; opt in per task below
[models.tasks] # which tier each task uses (cheap | flagship | reasoning)
research = "flagship"
normalize = "cheap"
query = "flagship"
# thesis = "reasoning" # e.g. route the hardest judgement calls to opus
# …
[models.effort] # subscription backend only: claude -p --effort per task
thesis = "medium" # every unlisted task defaults to "low"
synthesize = "medium" # compile stays low — high effort exceeds the timeout
[pricing."claude-sonnet-5"] # USD per 1M tokens — drives cost tracking
input = 3.0
output = 15.0
[embedding]
provider = "auto" # auto | local | voyage
local_model = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2" # 384-dim, uk+en, ONNX
voyage_model = "voyage-3.5" # 1024-dim, needs VOYAGE_API_KEY
dim = 1024 # vector dim when using voyage
local_dim = 384 # vector dim when using local
[recall] # UserPromptSubmit memory injection (see "Agent memory")
min_similarity = 0.42 # measured gate; dedup, devlog_half_life_days, routing_hint also here
annotate = true # prefix excerpts with confidence / staleness / event type
[why] # decision memory (see "Why is this code the way it is?")
guardrail = true # PreToolUse: warn before editing a file with decision history
guardrail_exclude_types = ["chore", "docs"] # types to STAY QUIET about; everything else warns
guardrail_max_events = 2 # max past decisions quoted per warning
# guardrail_types = [...] # deprecated whitelist, still read for one release
[capture]
auto_digest_batches = 1 # SessionStart flush: max cheap digest batches (0 = off)
subagents = true # SubagentStop: record what each subagent changed
precompact = true # PreCompact: sweep decisions that touched no file
precompact_max_chars = 20000
[consolidate]
period = "week" # week | month
min_age_days = 14 # only consolidate events older than this
auto = false # also run at SessionStart when true
route = true # attach each consolidated event to its matching topic (zero LLM)
route_min_similarity = 0.46 # cosine gate for routing — re-measure per embedding model
route_max_topics = 1 # attach each event to at most N topics
[federation] # see "Federated memory"
enabled = true # read peers registered with `tyamka peers add` (none by default)
peer_timeout_ms = 500 # per-peer wall clock; a slow peer is dropped, never awaited
[maintain] # see "Maintenance budget"
enabled = true
window_hours = 24 # rolling budget window
max_calls_24h = 8 # max LLM calls automatic maintenance may make per window
max_usd_24h = 0.50 # and the USD ceiling; whichever binds first stops the run
jobs = ["vectors", "paths", "hygiene", "peers", "digests", "consolidate"]
[llm]
backend = "api" # api | subscription
subprocess_timeout_s = 300 # per `claude -p` call; raise it if you route tasks to high effort
[retrieval]
top_k = 12
rrf_k = 60 # Reciprocal Rank Fusion constant
rerank_model = "cross-encoder/ms-marco-MiniLM-L-6-v2" # deep-depth rerank
[research] # personas per research mode
standard_personas = ["academic", "technical", "applied", "news", "contrarian"]
deep_extra = ["historical", "adjacent_fields", "data_stats"]
max_extra = ["methodological", "speculative"]
[volatility] # freshness windows (days) by topic volatility
LOW = 365
MEDIUM = 90
HIGH = 14
[confidence] # weights for the in-code confidence formula
w_count = 0.35
w_diversity = 0.25
w_recency = 0.25
w_evidence = 0.15
conflict_penalty_per = 0.1
conflict_penalty_cap = 0.4
Embeddings. With provider = "auto", tyamka uses Voyage when VOYAGE_API_KEY is set and the local model otherwise. The default local model is multilingual (sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2), so recall works for non-English prompts. It is served through fastembed (ONNX); a model outside fastembed's registry still works but falls back to torch, which is roughly 8x slower per call and says so on stderr. The chosen provider's dimension sizes the vector table at tyamka init — switching providers (and therefore dimension) on an existing wiki requires re-initializing it. Pick your embedding provider before you build up a wiki.
Changing the local model / reindexing. A wiki records which embedding model built its chunk vectors. Change local_model and the next indexed call refuses to run rather than fuse incompatible vectors — rebuild them with tyamka reindex --embeddings (local, zero LLM cost, one-time). Same-dimension swaps (e.g. another 384-dim model) keep the vector table; a different dimension needs a re-init.
Cost & budgets. Every call is priced from [pricing] and recorded. tyamka research/tyamka thesis take --budget <usd>: once cumulative session spend reaches the cap, no new persona wave starts and the session is marked PARTIAL (resume it later with --resume). Unknown models price at 0.0 — add them to [pricing] to track their cost.
Choosing an LLM backend
tyamka can run its LLM calls two ways, selected in config.toml:
[llm]
backend = "api" # or "subscription"
api(default) — the Anthropic developer API. Needs an API key / credit balance from console.anthropic.com (billed separately from a Claude subscription). Efficient, with a hard structured-output guarantee and native web search. Recommended for heavy research or when extraction robustness matters.subscription— routes calls through the Claude Code CLI (claude -p), using your Claude subscription (no API credits). Requires theclaudebinary installed and logged in (ant/Claude Code). Caveats: every call loads the Claude Code harness (~22K tokens of overhead), so atyamka researchfan-out consumes subscription usage limits quickly — best for light/occasional use. Structured extraction is prompt-and-validate (slightly less robust than the API path), each call is slower, and the cost shown bytyamka statsis a notional API-equivalent estimate, not a real charge.
Per-task effort (subscription only). [models.effort] maps a task to claude -p --effort
(low | medium | high); every unlisted task defaults to low. compile must stay low —
high effort makes its structured-output call exceed [llm] subprocess_timeout_s. The api backend
ignores effort. Combine with a reasoning tier ([models.tasks] thesis = "reasoning") to send the
hardest judgement calls to a stronger model; raise subprocess_timeout_s if you do.
Keeping a wiki fresh
Topics carry a volatility and a staleness window. tyamka refresh lists lapsed topics; --run re-researches them. A daily cron entry:
# Re-research any stale topics every day at 03:00
0 3 * * * ANTHROPIC_API_KEY=sk-ant-... TYAMKA_HOME=$HOME/wiki /path/to/uv run tyamka refresh --run
MCP server
tyamka serve-mcp exposes the wiki over the Model Context Protocol (stdio transport) for use by MCP-capable agents. Registered tools: search_knowledge, get_article, list_topics, ingest_source, start_research, evaluate_thesis, find_related, get_activity_context, get_stats, generate_output — each calling the same service functions the CLI uses.
search_knowledge(question, depth, mode, scope) defaults to mode="extract" — zero LLM calls, returns cited excerpts for the calling agent to synthesize in its own context. Pass mode="synthesize" to have the wiki's own LLM write the prose answer instead (the tyamka query behavior). scope (all | articles | devlog) controls what's searched; depth (quick | standard | deep) controls ranking effort only.
ingest_source(target, topic, new_topic) ingests a source and, when topic is given, attaches it to that topic (the same internal-source bridge as the CLI's tyamka ingest --topic) so it compiles into that article — no web search.
Example client entry (Claude Desktop / any MCP client):
{
"mcpServers": {
"tyamka": {
"command": "uv",
"args": ["run", "tyamka", "serve-mcp", "--home", "/absolute/path/to/wiki"],
"env": { "ANTHROPIC_API_KEY": "sk-ant-..." }
}
}
}
Development
uv run pytest # full suite — runs with NO live API keys (providers are faked)
uv run ruff check . # lint
uv run ruff format . # format
uv run mypy tyamka # strict type-check
The test suite makes no network calls: LLM and embedding providers sit behind Protocols and are injected as fakes, HTTP is stubbed, and each test gets a fresh temporary SQLite database.
Documented assumptions & limitations
These are deliberate scoping decisions, not oversights:
- Secrets are environment-only. API keys are never written to or read from
config.toml. - Capture's secret redaction is prefix-anchored and best-effort. It masks shapes carrying an
unambiguous vendor marker (
sk-,AKIA,ghp_/github_pat_,xox*-,AIza, JWTs,Bearer) and deliberately leaves git object ids and sha256 content hashes alone — capture writes those on purpose. It is a last line of defence, not a licence to paste credentials into a session. - An unknown key in
config.tomlis reported, not fatal. The installedtyamkaCLI drifts from whatever sources are on disk until you reinstall it, so opening a wiki with an older build than the one that wrote its config is ordinary; a key this build doesn't know is named on stderr and ignored rather than taking the whole wiki down. A typo still announces itself. - Federation is bounded twice. Each peer gets
peer_timeout_ms(default 500 ms, covering the database open as well as the read), and the whole fan-out gets 3× that — hooks have a hard wall-clock budget, and a per-peer bound alone lets N registered peers multiply into N × timeout. Peers still waiting when the total budget is gone contribute nothing, exactly like a slow one. - Raw sources are immutable. Re-ingesting updates provenance, never the stored text; the auditor checks citation quotes against that immutable text.
- Injection defense is uniform. Every place untrusted or model-generated text is wrapped in
<source_data>seals the delimiter first (sharedseal_source_data), so a crafted source can't break out of the data envelope. tyamka researchshows a live agent table;tyamka thesisdoes not (thesis runs to completion and prints its verdict).- The static-site export renders article Markdown as escaped, pre-wrapped text — there is no Markdown→HTML dependency, so bodies are shown verbatim (and safely escaped) rather than rendered.
- Dev events compound two ways. They roll up into the time-bucketed
development-logarticle (tyamka consolidate), and each consolidated event is also routed — a conservative local embedding match ([consolidate] route, zero LLM) — into its most relevant compiled topic, whose article then cites it. Below the similarity gate an event stays dev-log-only; you can always bind one by hand withtyamka attach. The gate (route_min_similarity, default 0.46) is a conservative starting value, not a measured one — raise it if routing pulls weakly-related events into articles, lower it if genuine matches are missed, and re-measure after changing the embedding model (like[recall] min_similarity). - The recall similarity gate is tuned for the default
paraphrase-multilingual-MiniLM-L12-v2embedder, on one corpus. The 0.42 threshold was measured on a live wiki (47 chunks) against 18 relevant, 12 adjacent-technical and 12 off-topic uk+en prompts: it keeps 13/18 relevant and admits 2/24 noise. The bands overlap — no threshold separates them cleanly — so 0.42 is a chosen trade-off on that curve, not a clean boundary, and it was measured against one wiki's content. If you switch embedding models, re-measure it and runtyamka reindex --embeddings: the gate is a per-model number and a threshold from the wrong model either mutes recall entirely or injects noise into every prompt. - Dev-event attribution is file-level, and events carry no commit anchor. Capture records the files a change touched (that is what
tyamka whyindexes), not hunk line ranges, and deliberately captures uncommitted work — so an event is not tied to a branch or a SHA.tyamka why <path>:52therefore accepts the line and ignores it. - Subagents do not receive wiki memory. The
SubagentStarthook'sadditionalContextoutput does reach a subagent's own context (verified against Claude Code's hooks reference), but the hook's stdin payload carries noprompt/task field to retrieve against — its documented fields aresession_id,transcript_path,hook_event_name,permission_mode,agent_id, andagent_type, and the event's schema isn't otherwise documented (tracking issue: anthropics/claude-code#19170) — so there is nothing to key retrieval on.SubagentStopcapture (recording what a subagent changed) is unaffected and does work. - An unstamped or mismatched peer contributes nothing to vector paths.
recall,query --extract, andsearch_knowledgeonly admit a peer whose stampedwiki_meta.embedding_modelmatches the local one —tyamka reindex --embeddingson that peer, run by its owner, is what changes that.why,changelog, andimpactare unaffected: they never touch a vector. tyamka syncexports; it does not synchronize. There is no server and no network code:push --dry-runwrites a file andpushwithout it exits 2.sync_stateexists and is read, but nothing advances it, because only a server's acknowledgement legitimately could.remote_keydoes not resolve~/.ssh/configaliases.git@github-personal:you/x.gitnormalizes togithub-personal/you/x, notgithub.com/you/x— resolving it would mean reading the user's ssh config, and the alias is genuinely how that machine reaches the server. The cost is that one repository can appear as two projects, which is recoverable; the opposite mistake (folding two different repositories into one key) is not.- The Viewer never migrates a schema. It opens every database read-only, so a wiki whose owner
has not run
tyamka maintainsince upgrading still lacksUNIQUE(topic_id, version)— the Viewer keeps itsMAX(version)→MAX(id)workaround for exactly that case. - A peer without
dev_event_filescontributes nothing towhy,changelog, orimpact. Those commands index by file path; a peer that predates that table, or has simply never captured a dev event, is skipped rather than erred on.tyamka peers listnames the fix — run in that project, by its owner, since repairing a peer from here would itself be a cross-wiki write.
Deferred toggles (not built)
- MCP
streamable-httptransport (stdio only for now). - A second
LLMProviderimplementation beyond Anthropic. - Multiple wikis per process.
Viewer UI (viewer/)
A local, strictly read-only Spring Boot 4 + React web UI over every tyamka database on the
machine — the global wiki ($TYAMKA_HOME, default ~/wiki) plus any project-local
.tyamka/wiki.db found under the configured scan roots (default ~/dev, depth 3).
Auto-start. Inside a checkout of this repo, a SessionStart hook brings the viewer up for you —
it runs ${CLAUDE_PROJECT_DIR}/hooks/viewer-autostart.sh, so in any other project it no-ops.
Idempotent and non-blocking, it never delays session start:
- If something is already on the port, it does nothing (one shared instance serves every wiki).
- If the jar is built and
javais on your PATH, it launches it in the background. - If the jar isn't built yet, and both
javaandnpmare on your PATH, it runs a one-time./gradlew bootJarin the background; when the build finishes a few minutes later, that same background process launches the viewer — no new session needed. Missing toolchain → it silently no-ops (build the jar manually, below).
Controls: TYAMKA_VIEWER_AUTOSTART=0 disables it; TYAMKA_VIEWER_PORT overrides 8080;
launch/build output goes to $TMPDIR/tyamka-viewer.log (/tmp/tyamka-viewer.log when
TMPDIR is unset). Auto-start is macOS/Linux only — the hook is a bash script, so on Windows it
simply no-ops and you use the manual launch below. Note that SessionStart hooks run in a
non-interactive shell — if your java/npm come from a version manager (sdkman, nvm) that only
patches your interactive shell, the hook won't see them and will no-op; add them to your
non-interactive PATH, or just launch manually:
cd viewer && ./gradlew bootJar && java -jar build/libs/tyamka-viewer.jar
# open http://127.0.0.1:8080
Dev mode: ./gradlew bootRun + cd frontend && npm run dev (Vite proxies /api to :8080).
Views per wiki: dashboard (counts, confidence distribution, staleness), topics & articles with
citations/conflicts/related, raw sources with provenance and cited-by, research sessions with
persona findings and thesis verdicts, LLM spend charts, the dev-cycle log, the topic graph, and
FTS5 search. The viewer opens SQLite strictly read-only (WAL readers don't block writers) and
never migrates the schema — tyamka/storage/schema.sql stays Python-owned. The copy at
viewer/src/test/resources/schema-test.sql must be re-trimmed when the Python schema changes.
Metadata
Release files for tyamka 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tyamka-0.6.0.tar.gz | 635.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tyamka-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 864.8 kB
Release files / tyamka-0.6.0.tar.gz
| Download URL | tyamka-0.6.0.tar.gz |
|---|---|
| Size | 635.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9cd6840aa2beba6bb05a790890e63112fbaf4520b190f715bceda2bdd3978773
|
|
BLAKE2b-256 checksum How to use checksums |
371db0511d09667f40a5226a70fa69b3a414aef0d4408cea99a8afbb13790501
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 3, 2026.
Transparency logRelease files / tyamka-0.6.0-py3-none-any.whl
| Download URL | tyamka-0.6.0-py3-none-any.whl |
|---|---|
| Size | 229.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2643bdb9a113beea17bc33ed9a9a6f9a690278a7d2a9df9a73cc3a7c9aa7d118
|
|
BLAKE2b-256 checksum How to use checksums |
6794b33073979d45dd266cfda51c4140c907015103850d119179be048aa08a9e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 3, 2026.
Transparency log