Issues and PRs welcome at github.com/AgamiAI/agami-core.
This project is fair-code (source-available) and is offered under a dual-license model: the Agami Functional Use License for the community, and a separate commercial license. For that model to hold, every external contribution has to come in with the rights that let us ship it under both licenses.
So before your first contribution can be merged, you sign a short Contributor License Agreement (CLA.md). In one sentence: you give Agami AI permission to license your contribution on any terms — including the fair-code FUL and a commercial license — and your contribution comes as is, without warranty or liability on your part. The full text is short; please read it — it includes a warranty/liability disclaimer.
How to sign — no separate account, no form. The first time you open a PR, our CLA bot comments on it with a link to the agreement and asks you to sign. You sign by replying with a single comment:
I have read the CLA Document and I hereby sign the CLA
The bot records your signature (stored in this repo) and flips the CLA status check to green; it stays green for all your future PRs. Until it's signed, the check blocks merge — that's the only thing it gates.
Maintainers and first-party (Agami AI) authors are allowlisted, so internal commits aren't gated; the CLA is for contributions from outside the organization.
The same gate runs in CI on every PR — ruff (lint + format), the test suite, and gitleaks (secret scan). To catch problems before you push, install the local hooks once.
Prerequisite — install uv (the only thing you need globally):
# macOS / Linux:
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows (PowerShell):
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
# any platform, if you prefer a package manager:
# brew install uv · winget install astral-sh.uv · pipx install uv
# docs: https://docs.astral.sh/uv/getting-started/installation/Everything else rides on uv — there's nothing else to install globally. From the repo root, a
tiny cross-platform task runner (dev.py) wraps it all:
uv run dev.py setup # once: wire the pre-commit hooks (ruff + gitleaks on commit, tests on push)
uv run dev.py check # the whole gate locally — ruff + tests + gitleaks (same as CI)
uv run dev.py cover # did the lines I changed get tested? (patch coverage)dev.py shells out to uvx (which fetches ruff / pre-commit / pytest on demand), so it runs the
same on macOS, Linux, and Windows. Other tasks: test, lint, fmt. After setup, the hooks run
automatically — ruff + gitleaks on every commit, the test suite on every push. They're a
convenience (bypassable with git commit --no-verify); CI is the real, unbypassable gate.
Prefer the raw tools? dev.py is only a wrapper — the equivalents are below.
The suite imports the agami-core library, so install it editable with its [model,server] extras
(that pulls in pydantic / pyyaml / sqlglot plus the server deps; DB-driver tests skip cleanly
without a database). uvx wires it up for the run:
uvx --with pytest-cov --with pytest-xdist --with-editable "packages/agami-core[model,server]" pytest tests/ -q -n auto-n auto runs one worker per core, the way CI does. A test that passes alone and fails under it is
sharing state with another test — fix the sharing rather than dropping the flag.
The privacy test (tests/test_privacy_no_network.py) is a contract: no shipped script may make a
network call — adding a network-egress primitive fails the build.
uv run dev.py cover reports coverage of the lines your PR touched (fails on changed lines that
no test exercises) — the quickest way to confirm a change is tested, regardless of overall coverage.
Under the hood that's:
uvx --with pytest-cov --with pytest-xdist --with-editable "packages/agami-core[model,server]" \
pytest tests/ -q -n auto --cov=plugins --cov=packages/agami-core/src --cov-report=xml
uvx diff-cover coverage.xml --compare-branch=origin/maincd tests/integration
docker compose up -d
./test_postgres_e2e_cli.sh # native CLI (psql)
./test_mysql_e2e_cli.sh
./test_postgres_e2e_duckdb.sh # DuckDB (skipped if duckdb not on PATH)
docker compose down -vClaude Code's plugin marketplace caches each plugin by version number. The cache key for any user who installed agami-core@1.1.0 is pinned to that version — Claude Code does not re-fetch source files until the version changes, even if the upstream main branch has moved on.
This has a real consequence: if we rename a skill, change file layouts, or remove a Bash invocation pattern from .claude/settings.json and don't bump the version, every user who installed an earlier version stays on the old code forever. They'll see stale slash commands, hit broken file paths, and have no obvious way to invalidate their cache short of deleting ~/.claude/plugins/cache/agami/agami-core/<old-version>/ by hand.
So: bump the version on any commit that changes user-visible behavior.
The version lives in four places across three files. They must always match:
.claude-plugin/marketplace.json—metadata.versionandplugins[0].versionplugins/agami/.claude-plugin/plugin.json—versionpackages/agami-core/pyproject.toml—version, the one published to PyPI.release-pypi.ymlfails the publish when the release tag doesn't match it, so a bump that misses this file doesn't ship a wrong version — it doesn't ship at all.
Use semver:
| Change | Bump |
|---|---|
| Bug fix, doc-only change, internal refactor with no user-visible behavior shift | patch (1.1.0 → 1.1.1) |
| New feature, new skill, new database type, new optional flag — anything additive | minor (1.1.0 → 1.2.0) |
| Skill rename, file-layout change, removed flag, default behavior change, anything that breaks an existing install | major (1.1.0 → 2.0.0) |
Semver is the contract: a major bump is the signal that says "this will break your config — you'll need to reinstall / re-migrate". Don't fold a breaking change into a minor bump.
The most common mistake is renaming a skill (e.g., init → agami-init) or changing a file path (e.g., <artifacts_dir>/local/<profile>/ → <artifacts_dir>/<profile>/) without bumping. Users installed at the old version see neither rename. Always bump on those changes.
- Slash command name changed → bump
- File the skill writes moved → bump
- Allowlisted Bash command removed from
.claude/settings.json→ bump - New required flag → bump
- New SKILL.md
when_to_usetrigger phrase added → patch (additive) - New optional flag → patch (additive)
- Typo fix in a SKILL.md description → patch
- Test-only or scripts/README-only change → no bump needed
When in doubt, bump. Patch bumps are cheap; stale caches in the wild are not.
We do auto-migrate on layout changes (e.g., v1.0 single-file → v1.1 directory → v1.2 split). That doesn't replace the version bump — it complements it. Without the bump, the new code that does the migration never reaches the user's machine. Bump first; migration code runs on next invocation.
When opening a PR that warrants a version bump, the checklist is:
.claude-plugin/marketplace.json— bump bothmetadata.versionandplugins[0].versionto the same string.plugins/agami/.claude-plugin/plugin.json— bumpversion.packages/agami-core/pyproject.toml— bumpversionto that same string.- Add a note to the PR description summarizing the user-visible change and which version it lands in.
Publishing is a separate, deliberate step: merging the bump ships nothing. release-pypi.yml and
release-image.yml both trigger on release: published, so PyPI and GHCR only move once a GitHub
release is cut with a v<version> tag matching packages/agami-core/pyproject.toml.
Record notable user-visible changes in CHANGELOG.md (Keep a Changelog format) under the version that ships them.
A PR that touches plugins/ or packages/ must also touch CHANGELOG.md, or the changelog
entry check fails. Add your entry under ## [Unreleased]; the release PR later renames that
heading to the version.
Tests, docs, dev/ tooling and workflows are not gated — none of them reach a user's machine on
an install. If a shipped change genuinely has nothing user-visible to say (an internal refactor, a
comment, a docstring typo), apply the no-changelog label to the PR and the check passes.
The rule exists because of what 0.7.0 cost: 90 commits landed between v0.6.9 and v0.7.0 with an
empty [Unreleased] section, so a minor release — two new skills, four new library modules, a new
CI exit contract — reached the cut with no notes at all. Reconstructing them from nine merged PRs
was slow, and worse, it was wrong in a way writing-at-the-time wouldn't have been: the notes shipped
a CLI flag that does not exist, because a summary written weeks later describes what someone
remembers building rather than what shipped. You are the only person who will ever have the context
to write your entry correctly, and that moment is the PR.
Once it's live, the link will appear here and in agami-connect/SKILL.md.