# Contributing to Agent Fleet Thanks for your interest. This is a small project with a single maintainer, so a few conventions keep things smooth. ## License of contributions By submitting a contribution you agree it is licensed under the project's **Apache License, Version 2.0** (see `LICENSE`). Apache-2.0 includes an explicit patent grant; no separate CLA is required. ## Ground rules - **Never commit secrets.** OAuth secrets, `AF_MASTER_KEY`, cookie secrets, and allowlists live in git-ignored files (`deploy/compose/.env`, `deploy/local/oauth.env`, `allowed-emails.txt`). Double-check `git diff` before committing. Compiled binaries are git-ignored too. - **Install the pre-commit hook, once per clone** (its worktrees share it): ```bash git config core.hooksPath .githooks ``` It runs the forbidden-token gate (`deploy/release/scan-forbidden.sh`) over the staged content only — a fraction of a second — and refuses the commit if a string this project keeps out of its history (real host names, customer names) is in it. `ci.yml`'s `release-scan` job runs the same scanner over the whole tracked tree on PRs and pushes, when the string is already in a commit. The local hook catches it before it enters history. Deliberate exception: a path glob in `deploy/release/forbidden.allow` — third-party content only. - **Keep the core deploy-agnostic.** Don't bake Docker/compose assumptions into the Control Plane. Deployment specifics belong behind the ports (Runtime, KeyCustodian, MetadataStore, AuthGateway) — see `docs/build/09-deploy.md`. - **Match the surrounding code.** Go: `gofmt` + `go vet` clean, `go test ./...` passing. Console: `npm run build` clean. - **Run `gofmt` before every commit that touches Go — this is a hard gate.** `ci.yml` runs `gofmt -l .` per Go module and fails the build on *any* unformatted file, so `go build` / `go vet` / `go test` passing is not enough (editors and auto-format often skip `_test.go` files, which is exactly what has slipped through). Before committing, run in each touched module and make sure it prints nothing: ```bash (cd control-plane && gofmt -l .) # lists unformatted files; empty = OK (cd workspace/agent && gofmt -l .) ``` Fix with `gofmt -w ` (or `gofmt -w .`). Same for `go vet ./...`. ## Building & running Local dev (host process): `deploy/local/run-dev.sh`. On-prem container deploy: `deploy/compose/` (see its `README.md`). Build paths on the dev host are non-standard: Go is at `$HOME/.local/go/bin`, Node via nvm. The compose image build is self-contained (multi-stage) and needs neither on the host — only Docker. ## Commits & PRs **`main` is the trunk** (and the default branch). Day-to-day changes go through a work branch → pull request → `main`; do not push them directly to `main`. Issues are optional; link the related issue in the PR when one exists. The maintainer checks the changes and verification results, then merges after the required CI checks pass. Approval from another person is not required. **"done" means merged into `main`**. Agents must not create branches on their own — the Console hands each worktree session its own branch. Remote: `git@github.com:k-k1/agent-fleet.git`. Hosted CI comes in two tiers. `ci.yml` (gofmt / vet / test / build) runs on **pull requests targeting `main` and every push to it**. PR checks verify changes before they reach the trunk; push checks verify the integrated result. The repository is public, so its runners are free. `e2e.yml` and the contract workflows do spend an external LLM quota, so those stay concentrated on pull requests that touch their paths, their nightly / weekly cron over `main`, and manual dispatch (several contract workflows run on dispatch only). Either way, the local run above is still the per-commit check — hosted CI is the safety net, not the first line. See `docs/log/35-packaging.md` for the billing rationale behind this two-tier split. Hotfixes follow the same path as any other change: work branch → PR → `main`. Release tags, release builds and public distribution are all cut from `main`. Keep commits small and focused, and follow the format below. ### Commit message format Conventional Commits, written in **English** — both the subject and the body. PR titles and descriptions and GitHub issues are written in English as well. The commit history doubles as the design record; history before 2026-09-25 is in Japanese and stays as it is (do not rewrite it). ``` (): ← first line, no trailing period, imperative, ~50 chars ← after one blank line. What changed and why. For a behaviour change: root cause → fix → verification. Wrap at ~72 columns. Co-Authored-By: > ← trailer, separated by a blank line ``` - **type**: `feat` / `fix` / `docs` / `style` (formatting only, no behaviour change) / `refactor` / `perf` / `test` / `build` (baked-in CLI or dependency version bumps) / `chore` / `ci`. - **scope**: the main subject of the change. Real examples: `console`, `chat`, `cp` (= control-plane), `agent`, `mirror`, `tts`, `workspace`, `deploy`; docs use `docs(NN)` with the chapter number. Add one whenever you can. - **body**: for bug fixes and behaviour changes, record **root cause → fix → verification (how you actually checked)**. This project is verified against a live fleet, and the commit history is the only design record of that. - **migration**: schema changes (`control-plane/internal/store/migrations/`, and `migrations-pg/` beside it for Postgres) must be forward compatible, and say so in the body — the embedded migrator applies them automatically at Control Plane startup and there is no downgrade path. ### Attribution (the Co-Authored-By trailer) This project is built with Claude Code, Codex and opencode side by side. A commit an agent wrote carries a co-author trailer after a blank line, naming **the model that actually generated it** — attribution is by model, not by CLI. | Environment | Example trailer | |-------------|-----------------| | Claude Code | `Co-Authored-By: Claude Opus 4.8 ` (or `Claude Fable 5` etc., matching the running version) | | Codex | `Co-Authored-By: GPT-5.6 ` | | opencode | Attributed to the running model (Claude family → `@anthropic.com`, GPT family → `@openai.com`) | - The address is the vendor's `noreply@` domain (Anthropic = anthropic.com, OpenAI = openai.com, otherwise the model vendor's domain). Name the model version actually running — don't hardcode one. - **No session URL line.** The old `Claude-Session:` trailer is retired. Claude Code adds one by itself when driven over Remote Control; that is harmless and accepted (suppress it in Claude Code's own settings, not here). - **Always add `Co-Authored-By:`** when an agent was involved — at least one line, and several lines when several models or people were. It may be omitted only for a commit written purely by a human.