Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 57 additions & 91 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,93 +1,59 @@
# Agent Instructions

## 项目状态

实现进度以 [ROADMAP.md](ROADMAP.md) 为准,不要从本文件或代码自行推断。

不要为了「让仓库看起来有东西」写占位代码、半成品或假实现。

## 环境

Go 在 `$HOME/sdk/go1.26.5/bin`,staticcheck 在 `$HOME/go/bin`。两个目录都不在默认 PATH 里。开始工作先执行:

```bash
export PATH="$HOME/sdk/go1.26.5/bin:$HOME/go/bin:$PATH"
export GOPROXY=https://goproxy.cn
```

Go module proxy 必须走 `goproxy.cn`。`proxy.golang.org` 在这台机器上不通。

`/tmp` 是 tmpfs。任何 fsync 相关的测量放在 `/tmp` 里都是空转,数字作废。存储 benchmark 要把 `GOR_BENCH_DIR` 指到真盘。

## 仓库布局

```
docs/ 产品 spec —— gor 该满足什么(产品语言 + 领域语言)
design/ 设计 spec —— 系统该怎么实现(可用技术语言)
research/ 实测证据 —— 支撑 design 决策的事实
```

## 协作

每个 agent 在自己的 worktree 里干活。按批次推进,每批做完停下来报告,等评审通过再进下一批。

依赖安装(`go get` / `go mod tidy`)是 agent 的事,不要指望别人代劳。不要让两个 agent 同时写 `go.mod`。

评审意见和代码不一致时,直接说:「你说的和代码对不上」。不要把代码改成评审意见描述的样子。

提交前只检查和提交自己负责的文件。不要 push,不要开 PR。

进度以 [ROADMAP.md](ROADMAP.md) 为准,它是叙事 spec:讲「做什么、为什么」,是大步骤和它们的理由。GitHub 的 milestone + issue 是执行单元:讲「这件具体的事、现在什么状态、归哪个版本」。issue 不复述设计,只指向 ROADMAP 或 design 文档的对应小节。

## Spec 先行

先把方案写进 `docs/` 或 `design/`,再实现。实装追赶 spec,不是 spec 跟着实装走。文档里出现尚未实装的能力是正常的。

某篇文档与代码有显著差距时,在文内单列「差距」小节说明现状。**正文是 spec,差距是脚注。**

`docs/` 禁止技术语言(包名、函数签名、存储表结构)。那些归 `design/`。

## 不可协商的约束

这几条来自 [design/testing.md](design/testing.md),违反任何一条都会让确定性模拟测试整体失效——而 DST 是本项目的主要差异点:

1. 所有 I/O 在接口后面。
2. 时间通过注入的 `Clock` 获取。生产代码里出现 `time.Now()` 即视为 bug。
3. 组件是显式状态机,状态转换是可枚举的函数。
4. **跨调用的等待用 channel,不用 mutex。** mutex 阻塞在 `synctest` 里不算 durably blocking。这条连带禁用了 `x/sync/singleflight`。

ROADMAP 第 4 步(DST 骨架)必须在第 6 步(集群)之前。顺序不可协商——这四条约束无法事后加装。

## 测试

```bash
make test # 单元测试,单个 < 50ms,不起网络不起进程
make sim # 确定性模拟测试,慢,不进默认 test
make gen # 生成器端到端测试,起 go list 子进程,不进默认 test
make net # 真 TCP 的传输测试,不进默认 test
make lint # vet + staticcheck
```

改完代码务必跑 `make ci`。

改动涉及 `runtime` / `cluster` / `store` 时,迭代过程中就单独跑 `make sim`,不要等到最后。

禁止:真实外部依赖、真实时间(`time.Sleep` 做同步、轮询墙钟做断言)、`t.Skip` 掩盖偶发失败、新旧测试并存。

需要 Go 1.25+(`testing/synctest` GA 版本)。

## 注释原则

默认不写注释;用命名、类型、函数边界让代码自解释。只有当代码无法表达「为什么这样做」时才写——外部系统限制、关键不变量、反直觉选择。

注释在解释「做什么/怎么做」,就重构代码让注释消失。

注释不引用设计文档、issue 或任务编号——文档会改名移动,引用必然腐烂。历史归 git log。

公开 API 的 doc comment 是使用者契约,不属于本节对实现注释的限制;它只写可依赖的行为,不叙述实现。

## 设计原则

模型尽可能简洁,只包括必要的属性。

不加保护性特判——越死板,特例越多。
## Project Context

- Treat [ROADMAP.md](ROADMAP.md) as the source of truth for implementation
progress.
- Treat [CONTEXT.md](CONTEXT.md) as the source of truth for product language.
Use its terms.
- Work within the current product and compatibility contracts. Do not add
placeholder, partial, fake, or speculative features.

## Engineering Principles

- Study established products before designing a solution. Reuse proven
patterns and conventions when they fit the current requirements.
- Choose the simplest design that fully meets the current requirements.
- Grow the system in working layers. Do not trade a working product for
unfinished complexity.
- Keep modules small and keep different concerns separate.
- Check existing dependencies before adding code or a package. Prefer a
maintained library when it reduces complexity or improves reliability.
- Make architecture decisions for the long term. Do not create a stopgap that
is meant to be replaced later.
- Remove obsolete paths. Add compatibility code only when the product contract
requires it.
- Keep models small. Add only the properties that the current contract needs.

## Architecture Constraints

- Follow the architectural constraints in [design/testing.md](design/testing.md)
when changing runtime, store, or cluster code.
- Keep all I/O behind interfaces.
- Get time from an injected `Clock`.
- Use explicit state machines for components with concurrent behavior.
- Use channels for waits across calls. Do not use mutexes for this purpose.
- Keep the deterministic simulation foundation ahead of new cluster work.

## Documentation

- Write or update the product or design spec before implementation.
- Use `docs/` for product requirements and user language.
- Use `design/` for technical design.
- Use `research/` for measured evidence.
- Write repository documents in the simple English defined by
[docs/writing-style.md](docs/writing-style.md).
- When a document differs from the implementation, add a clear `Gap` section.

## Collaboration

- Work in a separate worktree.
- Work in reviewed batches. Stop after each batch and report the result.
- Keep changes within the assigned files and preserve unrelated changes.
- Write commit messages in simple English. State the actual change.
- Before handoff, inspect the diff and report the verification result.

## Verification

- Before handoff, run the repository CI gate: `make ci`.
- Report failed tests, missing tools, and environment limits. Do not hide them.
124 changes: 124 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Orleans Grain Runtime

This context defines the Orleans language used by gor. gor is a Go port of the
Orleans runtime model. Use these terms in product docs, design docs, examples,
and new code names. Keep one term for one concept.

## Grain model

**Grain**:
A stateful object identified by a GrainId. A Grain has state and behavior.
_Avoid_: entity, actor, service, worker

**GrainId**:
The stable identity of one Grain. It contains a GrainType and a GrainKey.
_Avoid_: identity, address, instance ID, object ID

**GrainType**:
The kind of Grain. Grains with different GrainTypes do not share a GrainId.
_Avoid_: class, model, category

**GrainKey**:
The application value that selects one Grain of a GrainType.
_Avoid_: ID, name, identifier

**Grain Reference**:
A typed value that names a Grain without creating it.
_Avoid_: proxy, handle, stub, pointer

**Call**:
A request to run one method on a Grain through a Grain Reference.
_Avoid_: message, invocation, packet

**Request Context**:
Small data attached to a Call. The called Grain can read it during the Call.
Request Context is not State and the Grain Runtime does not save it.
_Avoid_: header, context value, request property

**Call Filter**:
Shared policy that runs before or after a Call.
_Avoid_: interceptor, middleware

## Runtime model

**Grain Runtime**:
The part of the application that starts Grains, accepts Calls, keeps State,
and runs Reminders.
_Avoid_: engine, server, actor system, framework

**Activation**:
The live form of a Grain that can receive Calls. A Grain can lose its
Activation without losing its GrainId or State.
_Avoid_: instance, process, actor

**Deactivation**:
The end of an Activation. Deactivation does not delete a Grain's GrainId or
State.
_Avoid_: destroy, delete, terminate

**Lifecycle**:
The path from Activation to Deactivation for one Grain.
_Avoid_: object lifetime, process lifetime

## State and reminders

**State**:
The current data owned by a Grain. State describes the Grain now.
_Avoid_: status, condition

**Confirmed State**:
State that the Grain Runtime has accepted as the current value for a Grain.
_Avoid_: saved state, cached state, best-effort state

**Durability**:
The amount of Confirmed State that remains after a machine failure.
_Avoid_: speed mode, flush mode

**Reminder**:
A future Call that the Grain Runtime remembers for a Grain. A Reminder can
happen once or repeat on a period.
_Avoid_: timer, wake-up, scheduled task, job

## Call results

**Conflict**:
A result that says a write used old State. A newer State already exists.
_Avoid_: collision, race error, stale write error

**Unknown Result**:
A result where the caller cannot know if a Business Action happened. A timeout
or delivery failure can cause an Unknown Result.
_Avoid_: failed call, lost call, partial success

**Business Action**:
A change that the application asks a Grain to make.
_Avoid_: side effect, operation, command

**Safe Repeat**:
A Business Action that does not apply the same business change twice when it
runs more than once.
_Avoid_: idempotent action, exactly-once action

## Boundaries

**Application**:
The program that defines Grains and their business rules.
_Avoid_: client, consumer, user code

**Silo**:
A process that hosts a Grain Runtime and its Activations. In 0.1.0, one Silo
runs on one machine.
_Avoid_: node, machine, server, worker

**Single Silo**:
One Silo with local State. This is the main gor product in 0.1.0.
_Avoid_: standalone mode, local cluster

**Cluster**:
Several Silos that share Grain ownership. Cluster support is an optional
extension.
_Avoid_: multi-node runtime, shared service

**Ownership**:
The Silo responsible for serving a Grain in a Cluster.
_Avoid_: placement, assignment, shard owner
29 changes: 18 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
# gor

**A persistent, stateful runtime for Go.** A single binary, library-shaped, embeddable, designed for deterministic simulation testing from day one.
**A persistent Grain Runtime for Go.** It is embeddable, runs as one Silo,
and is designed for deterministic simulation testing.

> **Status:** single-process features are implemented and usable. Multi-node calls can be routed and forwarded to the node that owns the entity; neighbor failure is decided by direct probing and death voting; errors carry stable codes across nodes. Detailed progress: [ROADMAP.md](ROADMAP.md)
> **Status:** single-Silo features are implemented and usable. Cluster
> features are an optional preview. Detailed progress: [ROADMAP.md](ROADMAP.md)

## What this is

A Go library that makes objects with an identity, state, single-threaded execution, and crash recovery your programming unit. You write ordinary Go interfaces and ordinary structs; `gor` handles activation, call serialization, persistence, and scheduled wake-ups. Cross-node distribution is an optional extension, not the main line: it exists for workloads that have outgrown one machine, and single-node users are not asked to pay for it.
A Go library that makes a Grain with a GrainId, State, serialized Calls, and
restart recovery your programming unit. You write Go interfaces and structs.
`gor` handles Activation, Call ordering, persistence, and Reminders. A
future cluster is an optional extension, not the main line.

The idea comes from Microsoft Orleans' virtual actor model, but this is not a port of Orleans. The trade-offs are recorded one by one in the [ADR and design documents](design/README.md); the three most important:
`gor` is a Go port of the Orleans runtime model. The Go API uses Go forms,
but the product terms and runtime meaning follow Orleans. The main design
rules are in the [design documents](design/README.md):

- The programming model is typed at compile time, not `any` in, `any` out — proxies are generated from Go interfaces ([design/codegen.md](design/codegen.md)).
- Single-node is a first-class citizen, not a degenerate mode of clustering. No sidecar, no external database — `import` it and it works.
- One Silo is a first-class product. It needs no sidecar or remote service.
- Deterministic simulation testing is an architectural constraint, not a testing technique retrofitted afterwards ([design/testing.md](design/testing.md)). This is the main difference between this project and comparable implementations.

## Why it exists
Expand All @@ -29,13 +36,13 @@ Measured details: [research/landscape.md](research/landscape.md) (in Chinese).

## What it does not do

`gor` explicitly does not pursue these; the reasons are in [docs/vision.md](docs/vision.md):
`gor` does not provide these in 0.1.0; the boundaries are in
[docs/vision.md](docs/vision.md):

- No Orleans API compatibility layer, and no one-to-one correspondence of concepts.
- No general-purpose actor framework (no supervision trees, mailbox policies, or behavior switching — the Akka-style capabilities).
- No workflow DSL or orchestration graphs.
- No "unbounded horizontal scaling". The target scale is a single machine to a small cluster.
- No cross-entity transactions. A call that touches two entities and fails halfway fails halfway — `gor` gives no rollback and no outbox. If you need atomicity, make them one entity.
- No source or binary compatibility promise with Orleans.
- No Call Filters.
- No reentrant or interleaved Grain Calls.
- No cluster operation tools or unbounded scale.

## Documentation

Expand Down
Loading
Loading