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 asWorkerIOScope / 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 fromdocs/specs/code-style.md and are the issues most often raised in review. That file is authoritative for the full set of constraints.
cmd/does assembly and startup only. No domain decisions, admission policy, or protocol branches; the worker / coordinator entry points are justapp.Profilemanifests, and shared assembly lives ininternal/*/app/.core/does not depend directly on JSON-RPC, gRPC, etcd clients, database clients, or logging and metrics implementations.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.- Avoid
context.Contextincore/wherever possible. Cancellation, timeouts, TTL expiry, and deadline advancement are modeled as explicit events or input fields. - 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. - 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. - Do not introduce boundary-less catch-all directories such as
pkg/utils,common, orshared. - Avoid weak-semantics suffixes such as
Manager,Util,Data, andInfoin exported names; states, events, and commands should reflect domain semantics directly, e.g.WorkerCore.HandleTaskPhaseFinishedandPhaseTransitionResult(internal/worker/core/). - 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]anyand scattered string literals. - 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. - Distinguish “admission / protocol errors” from “terminal results after execution has started” up front; do not blend them into one return.
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.- 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.
- 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.
- 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 intodev; 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.
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.mdis 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.mdis only a few lines long and points toAGENTS.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 inpprof-blockx-worker-*,profile-*,pyspy-*, and analysis notes such asoptimization-priority.md) and aworktrees/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.mdand 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.
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.