Contributing
This page documents how to contribute to AI Harness — local dev setup, branching, the test/lint bar, harness-level validation, PR conventions, doc contributions, and CI expectations.
If you only want to file an issue or propose an idea, jump straight to Issues & Discussions.
Project shape
| What | Where |
|---|---|
| Go source | agent/, config/, harness/, hooks/, scripting/, tools/, cmd/ |
| Built-in artifacts | .harness/builtins/, .harness/plugins/, .harness/overrides/ |
| Examples | examples/ (notably examples/governed-agent/) |
| Docs (mdBook) | docs/src/ — published to https://htekdev.github.io/ai-harness/ |
| ADRs | docs/adr/ |
| Specs / roadmap | project/roadmap.md plus referenced specs |
| CI | .github/workflows/{ci,pages,release}.yml |
The runtime is intentionally small: a single Go binary, Go 1.25, ~5 direct dependencies. Anything that can be expressed as a Markdown artifact under .harness/ should be — keep the core minimal.
Local development
Prerequisites
- Go 1.25 or newer (
go version) - Git
- mdBook (only if you're editing docs) —
cargo install mdbookor download from the mdBook releases
Build & run
git clone https://github.com/htekdev/ai-harness
cd ai-harness
go build ./...
go run ./cmd/harness --help
Run all tests
go test ./...
This is the canonical pre-push check. Run it before every push.
Run with race detector and timeout (matches CI)
go test -race -timeout 5m ./...
CI runs the race-instrumented suite on ubuntu-latest, macos-latest, and windows-latest against Go 1.25. Local race runs catch the same regressions earlier.
Lint locally (matches CI)
go vet ./...
gofmt -l . # must produce no output
If gofmt -l . lists any file, run gofmt -w <file> before committing.
Validate the test harness
go run ./cmd/harness validate -v
This reports the registered tools, hooks, models, and any artifact-loading errors. See reference/cli.md for the full command surface.
Branching & worktrees
The repo uses a single long-lived branch: main. All work happens on short-lived feature branches off main.
Recommended pattern (mirrors how the maintainers work):
- Create an isolated worktree for the change so you don't disturb your main checkout.
- Branch naming:
<type>/<short-slug>where<type>is one of:fix/— bug fixfeat/— new featuredocs/— docs-only changerefactor/— internal restructuring with no behavior changechore/— tooling, deps, CItest/— test-only changes
Examples seen in recent PRs:
fix/agent-finish-reason-strict-guarddocs/reference-starlark-builtinsfeat/governed-agent-example
- Push the branch and open a PR against
main.
The test bar
A change is mergeable when all of the following hold:
go test ./...passes locally.go test -race -timeout 5m ./...passes locally on at least one OS.go vet ./...is clean.gofmt -l .produces no output.- The 6 CI checks are green: Lint, Test (Go 1.25, ubuntu-latest), Test (Go 1.25, macos-latest), Test (Go 1.25, windows-latest), Build, and Build mdBook.
- For runtime changes that affect artifact loading, hook dispatch, tool registration, or context assembly: a fresh
go run ./cmd/harness validate -vagainstexamples/governed-agent/(or your repro fixture) is included in the PR description. - New or changed behavior is covered by a Go test. Bug fixes start with a failing test.
There is no "skip CI" or "merge red" path. If a check is flaky, fix it on its own PR before merging the change that surfaced it.
Authoring artifacts
If your change introduces or modifies a tool, hook, or context source:
- Tools must follow the schema in
reference/tool-artifact.md. Entry point isdef run(args); return shapes and the Starlark dialect are both documented there. - Hooks must follow
reference/hook-artifact.md. Entry point isdef handle(event, payload). Decisions returnblock(reason)/allow()/modify(payload)(or the equivalent dict form). Usepayload["name"]inwhen:predicates — not baretool_name. - Built-ins available inside Starlark are catalogued in
reference/starlark-builtins.md. Notably:type(v) == "string"— there is noisinstance. - harness.md frontmatter fields are catalogued in
reference/harness-md.md.
When you add or change a built-in, a hook event, or a CLI flag, update the corresponding reference page in the same PR.
Documentation contributions
The site is an mdBook rooted at docs/src/ and published by .github/workflows/pages.yml.
Local preview
cd docs
mdbook serve
Open http://localhost:3000. Edits hot-reload.
Structure
docs/src/SUMMARY.mdis the table of contents. Every new page must be linked from it — orphan pages are not allowed.concepts/— what the system is and whyguides/— how to do specific tasks (writing a tool, writing a hook, deployment, observability, etc.)reference/— exhaustive schemas (CLI, harness.md, tool/hook artifacts, Starlark built-ins)examples/— narrated end-to-end walkthroughs ofexamples/project/— meta documentation (this page, roadmap, ADR index)
Style
- Cross-link liberally. Every reference page links to the relevant concepts, guides, and other reference pages.
- Code blocks should be runnable as-is when feasible.
- When documenting Starlark, prefer
type(v) == "string"patterns and the canonical decision builtins. - Keep a single source of truth: if a fact lives in
reference/, link to it from concepts/guides rather than duplicating.
PR conventions
Title
Conventional Commits, scoped where useful. Examples:
fix(agent): detect finish_reason=length truncationdocs(reference): complete Starlark built-ins referencefeat(hooks): add agent.stop eventchore(deps): bump actions/cache to v5
Description
Include, at minimum:
- What changed (one paragraph).
- Why — link the issue, ADR, or roadmap item.
- Test evidence — local
go test ./...output summary, plus any harnessvalidate -vsnippet when relevant. - Backward compatibility — call out any artifact-schema, CLI, or hook-payload changes explicitly. These are user-visible contracts.
Size
Smaller is better. If a change touches more than ~500 lines of non-doc code, consider splitting it into a stack.
Review
A maintainer will review every PR. The bar is correctness, contract clarity, and minimal-core discipline (see project/roadmap.md and the README's Naming and terminology section).
Merge
Merges are squash-merges. The squashed commit message becomes the canonical history entry — keep PR titles clean.
Releases & versioning
- The project follows Keep a Changelog and Semantic Versioning.
- Every user-visible change must add an
Unreleasedentry toCHANGELOG.mdin the same PR. - Tags (
vX.Y.Z) are cut frommainafter the changelog is finalized;.github/workflows/release.ymlbuilds artifacts. - Pre-1.0 the artifact schemas (
harness.md, tool/hook artifacts, Starlark built-ins) are still evolving. Breaking changes are allowed on minor bumps but must be flagged inCHANGELOG.mdand explained in a follow-up note indocs/src/project/.
Issues & discussions
- Bugs: open an issue with a minimal
harness.md+ steps to reproduce. Includeharness validate -voutput and the failing command. - Feature ideas: open an issue tagged
proposaldescribing the use case before sending a PR. For larger changes, propose an ADR underdocs/adr/. - Security: do not file security issues in public. Email the maintainers per the SECURITY policy in the repo root.
Code of conduct
Be respectful, be precise, and assume good faith. Disagreements are settled by reference to the spec, the roadmap, or a new ADR — not by volume.
See also
project/roadmap.md— phase plan and current prioritiesproject/adr-index.md— architecture decisions of recordreference/cli.md— every CLI subcommand and flagreference/harness-md.md—harness.mdfrontmatterreference/tool-artifact.md— tool schemareference/hook-artifact.md— hook schemareference/starlark-builtins.md— Starlark surface