Skip to main content
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. 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

1

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.
2

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.
3

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.
4

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.
5

Verify locally

make test runs the fmt check, go vet, and unit tests with coverage in sequence.
6

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.
7

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).

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.
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.

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.

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.
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:

Design principles

Expanded explanations and code examples for Sans-IO, Scoped Resource Context, and Occam’s Razor.

Repository layout

What goes in cmd/, internal/, api/, and python/.

Testing

Organization and commands for unit, E2E, and perf tests.

Local development environment

Setting up Go, uv, etcd, and dependency stubs.