Contributing¶
The usual path is a GitHub issue, then one change on a short-lived branch. A pull request without an issue is fine when the work was asked directly and is already small.
Read CONSTITUTION.md, docs/process.md, and AGENTS.md. Product
pages under docs/ describe what we ship.
Agent steps: .agents/skills/.
Writing¶
Use simple technical English and common words. Do not invent names for things that already have names. Do not invent features.
Docs, specs, README, and the MkDocs site use complete sentences and enough explanation that a new reader can set up and use the API without guessing. Short choppy fragments are wrong for docs. Tables are still good for endpoints, env vars, and fields.
Issues can be as long as they need to be. Include the goal, what to check when done, spec paths, and any implementation details already known. Do not squeeze the goal. Pull request bodies and commit messages stay short.
Code comments stay omitted unless asked.
Issues¶
Open an issue. That is the list of planned work.
Write it so a human and an agent can do the work without a meeting. Use as much text as that takes.
| Kind | Include |
|---|---|
| Request | Goal, what to check when done, product page paths. Known implementation details when you have them. |
| Bug | What happens, what should happen, product page path, how to reproduce |
Optional: Details (how to implement, if already known), Files (what this change will edit), Blocked by (issue that must land first). Use Files and Blocked by when splitting work so two people or agents can work at the same time.
The goal is the scope. Keep each change small. One goal, few files. If two changes would edit the same file, land shared code first.
Git¶
PRs only onto main. Short-lived branches. Squash merge. No merge
commits on main.
| Branch | issue-<n>-<slug> from latest main, or a short name if there is no issue |
| Scope | One change per branch and per pull request |
| Merge | Squash onto main, then delete the branch |
| Record | The squash message is the history. In-progress agent commits are not |
No develop branch. No force-push to main. Rebase onto origin/main.
Do not merge main into the branch.
One checkout is one branch. Two changes at the same time need another
folder (git worktree) or session, and must not edit the same files.
Commit when asked to implement, commit, or open a PR. Merge only when asked to merge. Default after a PR: return the URL.
Pull requests¶
- If there is an issue, the PR body starts with
Fixes #N. - Spec and code change together when they disagree.
- GitHub checks pass (
Check,Tests, andDocs). - Review, then squash-merge.
Reviewing a PR¶
- Only this change's goal?
- Spec and code agree?
- Tenant-scoped? No stored secrets? No Pi types on HTTP?
- Tests cover what the issue said to check?
- Any constitution or
AGENTS.mdrule broken?
Must fix = a missed goal, spec and code disagree, or a broken rule. Small style notes do not block unless they break a spec.
Tooling¶
uv only. Never pip, never a bare
python -m venv. Python 3.13+.
| Tool | What |
|---|---|
| uv | Python, deps, commands |
| ruff | Lint and format |
| ty | Types |
| pytest | Tests |
No git hooks. Run checks yourself. CI runs them on the PR.
Tests¶
Pytest in tests/. Specs say what is true. Tests land in the same
change as the code.
| We test | We do not |
|---|---|
| Public HTTP vs the spec | Pi internals or JSONL |
| Tenant isolation (wrong tenant is 404) | The agent loop |
| Unknown fields fail clearly | Features absent from product pages |
| Event log is the transcript |
Gateway tests mock Pi RPC. Local pytest uses SQLite.
Commands, what each suite contains, and what microvm needs
are in docs/tests.md.
GitHub CI runs everything that finishes in a couple of seconds:
format, lint, types, docs, unit tests, and fast e2e
(pytest -m "not slow"). No Firecracker on GitHub.
The full suite is local: ./scripts/check including
@pytest.mark.slow. Optional Postgres: APIPI_TEST_DATABASE_URL.
Do not add slow jobs to GitHub.
Before you commit¶
If docs changed, ./scripts/check --docs. Fast like GitHub:
./scripts/check --fast.
Do not commit if checks fail.
GitHub: Check (format, lint, types), Tests (pytest -m "not slow"),
Docs.
Releasing¶
Version is Hatch dynamic from src/apipi/__init__.py. Tags are
vX.Y.Z and must match __version__. Releases are intentional: do
not bump on every merge.
- Set
__version__and add a dated section inCHANGELOG.md. - Merge to
main. - Push an annotated tag
vX.Y.Z. Do not create the GitHub Release by hand. The Publish workflow builds the sdist and wheel, uploads them to PyPI with Trusted Publishing (OIDC), then opens the GitHub Release for that tag and attaches the artifacts. There is no PyPI password in GitHub secrets. Agent skill:release. - Ordinary PRs do not publish. Optional TestPyPI: run the Publish
workflow with
workflow_dispatchand repositorytestpypi(no GitHub Release).
Official guest images are not part of that tag. Dispatch the
Images workflow (.github/workflows/images.yml) when you want a
GitHub release of default and browser. It does not run on pull
requests.
Before the first upload, an owner must create the PyPI project (name
geki-apipi) and a Trusted Publisher: GitHub org GEKI-AI, repository
apipi, workflow publish.yml, environment pypi. Create that GitHub
environment too. For TestPyPI, add a second publisher with the TestPyPI
project and the same workflow. The wheel name is geki-apipi; import
apipi and the apipi CLI do not change.
0.1.0 squashed Alembic to one baseline. Later schema changes are
normal forward migrations. Do not squash again unless you mean to reset
the line.
Docs site¶
Separate from the apipi package. Do not add MkDocs to pyproject.toml.
Local Postgres for apipi migrate / apipi serve:
DATABASE_URL=postgresql+asyncpg://apipi:apipi@localhost:5432/apipi.