Packages
Add registry packages to a project, pin an immutable snapshot, and override a dependency with a local path or a git fork.
A Katari project declares its dependencies in katari.toml. Each name in
[dependencies].packages resolves through a pinned registry snapshot — a curated set of
(package, version) pins that are guaranteed to compile together — or through a local
[overrides] entry. The CLI fetches each pinned tarball, verifies its content hash, and records
the exact resolution in katari.lock.
Add a package
katari add tavilykatari add checks that the name is resolvable (present in the pinned snapshot, or covered by a
root [overrides] entry), rewrites the packages array in katari.toml, fetches and verifies
the pin, and refreshes katari.lock. katari remove is the same edit in the other direction.
After adding, import the package by its name and call its agents qualified:
import tavily
@"Search the web; failures degrade to a readable line instead of failing the run."
agent main(question: string) -> string with io {
use handler {
request prelude.throw(error: env.missing_secret | http.status_error | http.fetch_error | json.parse_error) -> never {
break f"search failed: ${json.stringify(value = error)}"
}
}
use tavily.provider(api_key = env.get_secret(key = "TAVILY_API_KEY"))
tavily.search(query = question)
}The env.get_secret call reads the API key from the project's env store — see
Secrets and credentials.
Pin a snapshot
The [dependencies] section names the registry and the snapshot every package resolves against:
[dependencies]
registry = "https://raw.githubusercontent.com/katari-lang/katari-registry/main"
snapshot = "snapshot-2026-07-17-c47a6e67"
packages = ["tavily"]- A snapshot is a consistent set: every package in it compiles against every other, with the
katari_compilerversion the snapshot declares. Once cut, a snapshot file is immutable — pin one and your resolution never shifts under you. This is the recommended setting. snapshot = "staging"points at the registry's mutable candidate set — the packages that will become the next snapshot. Use it for early access to a just-merged package, and switch back to the next immutable cut.
There are no per-package version constraints to solve: the snapshot already fixed every version,
so packages is a flat list of names.
Commit the lockfile
katari add / katari remove (and a resolve during katari apply) write katari.lock next to
katari.toml:
[lock]
version = 1
snapshot = "snapshot-2026-07-17-c47a6e67"
[packages.tavily]
source = "git"
url = "https://github.com/katari-lang/katari-package-tavily"
rev = "2590e15f6ef3852af3d361b019633340501911fe"
sha256 = "6d59a5fb4d86ef59e73c80531503b512f8f4f977a7a97c216451dec515de286b"Commit it: every non-path dependency is pinned to a git rev plus a verified tarball hash, so
every consumer of the repository gets the same byte-for-byte resolution. A path override is
recorded without a hash — a local path is mutable on purpose.
Override a dependency
An [overrides.<name>] table replaces where one declared dependency comes from, without touching
the snapshot pin for everything else:
[dependencies]
registry = "https://raw.githubusercontent.com/katari-lang/katari-registry/main"
snapshot = "snapshot-2026-07-17-c47a6e67"
packages = ["tavily", "discord"]
# Develop a package against the app that uses it:
[overrides.tavily]
path = "../katari-package-tavily"
# Or pin a fork; the rev must be a full 40-character commit SHA:
[overrides.discord]
git = "https://github.com/you/katari-package-discord"
rev = "b93423583ba35d85aca3f0de80823b24cb77f9f9"Each override sets path or git (with git requiring rev), and must name a dependency
declared in packages — a typo'd override is an error rather than a silently ignored table. A
git override's rev is deliberately a commit SHA, not a branch or tag: with no separate content
hash, the rev is the only thing pinning reproducibility.
What is in the registry
The first snapshot (snapshot-2026-07-17-c47a6e67) carries eight packages. Every exported agent,
request, and type is documented in the reference.
- ai — a provider-agnostic AI tool-calling loop: one
infer_stepseam with interchangeable Anthropic, Gemini, and OpenAI providers. - discord — a discord.js gateway client: a connection provider, watch / send agents, and file attachments in both directions (ships an FFI sidecar).
- e2b — run Python in a persistent e2b sandbox as a tool (ships an FFI sidecar).
- google_calendar — calendar tools (list / create / watch) over the OAuth refresh-token grant — pure Katari, no sidecar.
- imagegen — edit images with the Gemini image model as a tool (ships an FFI sidecar).
- slack — a Slack bot capability over Socket Mode: watch channel messages, post replies (ships an FFI sidecar).
- tavily — web search as a tool over the Tavily API — pure Katari, no sidecar.
- web — fetch a web page as a tool over
http.fetch— pure Katari, no key.
Where to go next
- The tutorial's Discord bot composes seven of these packages into one app.
- FFI sidecars — how a package like
e2bordiscordships TypeScript alongside its Katari source. - CLI —
katari add,katari remove, and the rest of the toolchain.