> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blockx.chaintable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Contributing

> How to submit changes to BlockX: spec-first workflow, the three design principles, a code convention cheat sheet, commit and PR conventions, and AI agent collaboration conventions.

BlockX is a spec-first repository: `docs/specs/` is the source of truth for behavioral semantics. To change behavior, change the spec first, then the code, and land both in the same PR. This page consolidates `AGENTS.md`, `docs/specs/code-style.md`, and the CI configuration into a single contributor checklist; `AGENTS.md` is the authoritative version of these conventions.

## The three design principles

The three principles constrain both specs and code. For expanded explanations and code examples, see [Design principles and code conventions](/en/architecture/design-principles).

**Sans-IO** is the default rule. A new subsystem starts by defining a pure in-memory core: explicit input events, state transitions, and output commands; RPC, UDS, storage, clocks, logging, and metrics all go into adapters. The test is whether the core can be fully replayed with table-driven tests without any IO. Worker, Coordinator, and Dispatcher all take this shape.

**Scoped Resource Context** is for subsystems centered on resource ownership and reclamation, the IO access subsystem being the typical case. Model Worker-level shared resources and task-level local resources as explicit scopes (such as `WorkerIOScope` / `TaskIOScope`); quota, cache, singleflight, deadlines, and cleanup are all bound to the scope lifecycle, and cleanup is a structural property of scope exit rather than "send a release command later". Use this when forcing a module into event/command streams would only make ownership blurrier.

**Occam's Razor** applies to both. Do not add a struct, interface, error code, queue, state, or service unless you can explain why the existing module / core / adapters / scope boundaries cannot accommodate it. If a behavior can be expressed neither as a small enumerable set of states, events, and commands nor as a small set of scope acquire/release rules, the design is still too vague; go back and fix the spec first.

## Submitting a change

<Steps>
  <Step title="Read the relevant spec">
    Find the file in `docs/specs/` that defines the subsystem (e.g. `worker.md`, `io-subsystem.md`, `call-execution-subsystem.md`), confirm how the behavior you want to change is currently defined, and understand its design intent.
  </Step>

  <Step title="Change the spec first when changing behavioral semantics">
    When protocol, lifecycle, timeout, or error-code semantics change, update the spec in the same PR before writing code. Structs under `api/` must not quietly become the new protocol truth.
  </Step>

  <Step title="Write the core logic and table-driven tests">
    Change the state machine, idempotency decisions, and phase advancement in `internal/<module>/core/`; put tests in `*_test.go` in the same directory, covering the happy path and the failure / timeout paths written in the spec.
  </Step>

  <Step title="Wire the adapter and protocol mapping">
    In `internal/<module>/adapters/`, translate core commands into side effects and repackage external callbacks as core events. Adapter tests cover only protocol mapping, error translation, and orchestration; they do not repeat the core's state combinations.
  </Step>

  <Step title="Verify locally">
    ```bash theme={null}
    go fmt ./...
    go vet ./...
    go test ./...
    uv sync --project python
    PYTHONPATH=python uv run --project python python -m unittest discover -s python/tests
    ```

    `make test` runs the fmt check, `go vet`, and unit tests with coverage in sequence.
  </Step>

  <Step title="Run the relevant E2E tests">
    Pick by the scope of the change: `go test -v -timeout 300s ./cmd/worker/...` (worker process E2E), `./cmd/bundle_worker/...`, `./cmd/syncinvoker/...`, `./e2e/contract/...`, `./e2e/system/...`. All of these require `uv sync --project python` first.
  </Step>

  <Step title="Open a PR">
    Target branch `dev`. In the PR description, state the purpose, the spec / module paths involved, the verification commands you ran, and follow-up work (see below).
  </Step>
</Steps>

## Code convention cheat sheet

The following items are distilled from `docs/specs/code-style.md` and are the issues most often raised in review. That file is authoritative for the full set of constraints.

1. `cmd/` does assembly and startup only. No domain decisions, admission policy, or protocol branches; the worker / coordinator entry points are just `app.Profile` manifests, and shared assembly lives in `internal/*/app/`.
2. `core/` does not depend directly on JSON-RPC, gRPC, etcd clients, database clients, or logging and metrics implementations.
3. `core/` does not read the current time, generate random values, or start goroutines itself. Time and random numbers are passed in by adapters as inputs; concurrency belongs to adapters.
4. Avoid `context.Context` in `core/` wherever possible. Cancellation, timeouts, TTL expiry, and deadline advancement are modeled as explicit events or input fields.
5. The output of `core/` is enumerable commands, decision results, or state snapshots; adapters execute them. The pattern is "input event → core decision → adapter executes command"; core never holds a reverse callback.
6. Interfaces are defined on the consuming side. Adapters depend on concrete types (`WorkerCore`, `CoordinatorCore`) when calling core and do not abstract interfaces for core; do not extract an interface before there is a second implementation or a need for a test double.
7. Do not introduce boundary-less catch-all directories such as `pkg/utils`, `common`, or `shared`.
8. Avoid weak-semantics suffixes such as `Manager`, `Util`, `Data`, and `Info` in exported names; states, events, and commands should reflect domain semantics directly, e.g. `WorkerCore.HandleTaskPhaseFinished` and `PhaseTransitionResult` (`internal/worker/core/`).
9. When an object's state can only be understood by combining several boolean fields, replace them with an explicit state enum; do not overuse `map[string]any` and scattered string literals.
10. Do not test errors by string matching. Stable semantics rely on error codes, enums, or named result types; expected business outcomes are modeled as explicit results in core rather than overusing `error`.
11. Distinguish "admission / protocol errors" from "terminal results after execution has started" up front; do not blend them into one return.
12. `api/` holds only protocol structs, message envelopes, and field conventions shared across modules, not handlers, storage models, or state machines; when changing a protocol, prefer adding optional fields or new enum values over silently changing the meaning of existing fields.
13. Model time semantics in one place. Slot TTL, task deadline, and the result retention window converge in the Worker module and its timer adapter; do not replicate "how to decide after a timeout" across multiple adapters.
14. In scope-oriented modules, whoever owns the resource is the source of truth; do not let the scheduling layer, hook layer, and backend adapters each maintain shadow counters.
15. The creation, exit, and reclamation paths of goroutines must be explicit; do not rely on process exit as a backstop.

## Commit and PR conventions

**Branch model.** Day-to-day development merges into `dev`; CI (`.github/workflows/test.yml`) runs only for PRs targeting `dev` or pushes to `dev`. The default branch on GitHub is `main`, but it lags noticeably behind `dev` (at the time of a local check, `main` stopped at #335 from 2026-06 while `dev` was at #495 from 2026-08), and image tags (`v1.0.x`, `dev-v1.0.x-n`) are all cut from `dev`. So: branch from `dev`, and open PRs back to `dev`. There are no PR / issue templates and no `CODEOWNERS` under `.github/`.

**Commit messages.** Imperative mood, one concern per commit (spec updates, core logic, adapter wiring, and tests as separate commits). Use the format `module: summary`, e.g. `worker: tighten slot idempotency`; recent repository history has both English and Chinese messages (`io: keep idempotent BlockDB writes retryable`, `deploy: 支持从 S3 pointer 加载 worker.env`). What matters is a clear scope prefix and a specific summary; never write just `fix`.

**PR descriptions must include at least:**

* Purpose: what problem it solves.
* The affected specs and module paths (e.g. `docs/specs/worker.md`, `internal/worker/core/`).
* The verification commands you ran (paste the actual commands).
* Follow-up work or known uncovered scenarios.
* When behavior changes, link the corresponding spec and describe the compatibility impact.

<Warning>
  **Do not force-push a branch that has an open PR or has been shared.** Once a PR is open, only append commits and push normally. `git push -f`, `--force`, and `--force-with-lease` rewrite history, break the association between reviewer comments and code on GitHub, and cause problems for colleagues who have already fetched the branch. The only exceptions are purely local branches that have never been pushed, or an explicit team agreement to squash before merging.
</Warning>

## Testing requirements

Go tests live in the same directory as the implementation (`*_test.go`); prefer table-driven tests for state machines, protocol mapping, and the core / adapter boundary. New behavior must cover both the success path and the failure / timeout paths written in the spec; when protocol or lifecycle semantics change, tests and the spec are updated in the same change. CI runs Go unit tests, Python unit tests, the Worker Process E2E split into 8 shards, the bundle worker / contract / system E2E, and the Perf E2E with `-short`. For test directory organization, commands, and E2E details, see [Testing](/en/development/testing).

## AI agent collaboration

* `AGENTS.md` is the authoritative repo-level collaboration document: directory conventions, build / test commands, coding style, design principles, testing and PR conventions. Both humans and AI agents follow it.
* `CLAUDE.md` is only a few lines long and points to `AGENTS.md`, so the two files do not drift.
* `.claude/` is in `.gitignore`; it is a local working directory and is not committed. What it currently holds locally is performance troubleshooting output (pprof / py-spy sample files in `pprof-blockx-worker-*`, `profile-*`, `pyspy-*`, and analysis notes such as `optimization-priority.md`) and a `worktrees/` directory (for agents to open git worktrees). It contains no shared agent configuration, commands, or hooks; do not treat it as a source of team conventions.
* When you have an agent change code, give it `AGENTS.md` and the relevant spec to read together; PRs produced by agents must meet the same commit / PR conventions and verification requirements described above.

## Related docs

blockx repository:

* `AGENTS.md` — the authoritative version of repository collaboration and development conventions.
* `docs/specs/code-style.md` — module boundaries, implementation constraints for Sans-IO / Scoped Resource Context, error and time semantics.
* `docs/specs/architecture.md` — system architecture and module breakdown.
* `docs/specs/worker-test-organization.md`, `docs/specs/python-test-organization.md`, `docs/specs/system-e2e-testing.md` — test organization conventions.
* `README.md` — the three design principles and repository structure.

On this site:

<Columns cols={2}>
  <Card title="Design principles" href="/en/architecture/design-principles">
    Expanded explanations and code examples for Sans-IO, Scoped Resource Context, and Occam's Razor.
  </Card>

  <Card title="Repository layout" href="/en/architecture/repository-layout">
    What goes in `cmd/`, `internal/`, `api/`, and `python/`.
  </Card>

  <Card title="Testing" href="/en/development/testing">
    Organization and commands for unit, E2E, and perf tests.
  </Card>

  <Card title="Local development environment" href="/en/development/getting-started">
    Setting up Go, uv, etcd, and dependency stubs.
  </Card>
</Columns>
