Skip to main content
Glama
Zorabi

Concordia

by Zorabi
README.md
# Concordia

> 面向 Codex 与 ZCode 的 local-first MCP 多代理协作控制面,支持单机 stdio 与可选的跨机器 Redis relay。

Concordia 以结构化任务、事件、租约和 Git worktree,将“规划与验收”与“实施”分开:**Codex** 创建任务、回答问题、审查交付;**ZCode** 原子领取任务、实施、按需使用其子代理,并提交可审查的 commit 与验证证据。状态保存在协调主机的本地 SQLite;双方既可在单机直接连接,也可在都没有公网 IP 时通过 Redis 中转。

它适合在一台机器或“远程 Codex + ZCode 执行主机”的两机拓扑中,可靠地协调一个或少量 Git 仓库,不依赖 GitHub Issue 或共享在线文档。

## 它解决什么问题

- **任务是契约**:目标、目标仓库、允许路径、约束、验收条件与交付项均为结构化字段。
- **事件是沟通**:进度、提问、回答、心跳、失败、返工和批准都是带序号的持久事件。
- **提交是证据**:ZCode 提交的 SHA、变更文件、检查结果和风险进入任务记录,供 Codex 审查。
- **租约防止双写**:过期重领后旧执行者会被围栏拒绝,不能继续写入事件或提交。
- **worktree 隔离写入**:每次领取使用独立 Git branch/worktree,避免并发任务互相污染。
- **事件驱动唤醒代理**:可选 `codex-waker` 与 `zcode-waker` 常驻进程在普通 Node.js 中监听事件;仅在需要审查、回答、领取或返工时启动对应代理 turn。

## 边界与非目标

当前版本面向**单协调节点、同一可信用户或团队、少量并发任务**。已实现 SQLite 持久化、任务状态机、Git 隔离、幂等写入、租约恢复、ZCode 插件、Codex 事件唤醒器,以及基于 Redis Streams 和角色签名的跨机器 relay。尚不提供:

- 多用户身份、仓库级 ACL 或租户隔离;
- 多协调节点高可用或 PostgreSQL;
- 多个独立 ZCode 执行机之间的路径映射和 Git 对象传输;
- Web Dashboard 或面向第三方的通用推送订阅;
- 自动合并、推送远程 Git、发布制品或部署;
- 强制 ZCode 使用某个子代理,或替代理制订实现计划;
- 映射为 ZCode 原生侧边栏任务。

合并、推送、发布与生产操作须由用户或上层协调者明确执行。

## 架构

### 单机模式

```text
Codex ── stdio MCP ─┐
                    ├── Concordia ── SQLite(任务、事件、交付物)
ZCode ── stdio MCP ─┘        │
                              ├── 目标 Git 仓库/.worktrees/<task>-zcode-a<attempt>
                              ├── codex-waker ──> Codex App Server(按事件启动审查 turn)
                              └── zcode-waker ──> ZCode CLI(按事件启动或恢复实施 turn)
```

### 跨机器 Redis 模式

```text
Codex MCP relay client ──┐
                         ├── 出站 TLS ──> Redis Streams/response keys
ZCode MCP relay client ──┘                         │
                                                  │ 出站连接
                                      Concordia relay coordinator(ZCode/Git 主机)
                                                  ├── 本机 SQLite
                                                  └── 本机 Git 仓库与 worktrees
```

远程模式必须把协调器部署在持有目标 Git 仓库的执行主机上。两端只需主动连接同一 Redis,无需公网 IP 或入站端口;`claim_task` 和 `submit_task` 仍在协调主机执行本机 Git/worktree 强校验。详细部署、令牌、Codex/ZCode 配置和安全限制见 [跨机器协调指南](docs/remote-coordination.md)。

这里有两个不同的目录:

- **Concordia 源码目录**:本仓库,包含 `src/`、构建产物与 `zcode-plugin/`;例如 `/absolute/path/to/concordia`。
- **被管理的目标 Git 仓库**:代理真正修改的业务项目;例如 `/absolute/path/to/example-app`。任务 `workspace` 必须是它的 Git 根目录。worktree 创建在这个目标仓库内,而非 Concordia 源码目录。

单机模式下,Codex 与 ZCode 可运行各自的 stdio MCP 服务进程;只要两端的 `CONCORDIA_DB` 指向同一绝对 SQLite 路径,便可共享状态。跨机器模式下,SQLite 只保留在协调主机本地,远程客户端通过 Redis relay 访问,禁止多台机器直接打开网络共享目录中的 SQLite 文件。

### 状态生命周期

`DRAFT` 是协议中保留的类型;当前 `create_task` 会直接创建并发布为 `READY`。

```text
READY ── claim ──> CLAIMED ── progress ──> RUNNING ── submit ──> REVIEW
                                             │                       │
                                             ├─ question ─> WAITING_INPUT
                                             │               └─ answer ─> RUNNING
                                             ├─ failed ───> FAILED
                                             ├─ cancelled ─> CANCELLED
                                             └──────────────── request_changes ─> READY(重新领取)
                                                                approve ─────────> APPROVED
```

终态为 `APPROVED`、`FAILED`、`CANCELLED`。只有 Codex 能创建、审批或要求返工;只有 ZCode 能领取和提交任务。

## 前置条件

- Node.js **22.13+**(使用 `node:sqlite`)。
- Git,且每个目标工作区必须是可访问的 Git 仓库根目录。
- npm。
- 可使用 Codex 和/或 ZCode 的 MCP 功能。
- 跨机器模式额外需要 Redis 7+ 或兼容服务;单机模式不需要 Redis 服务。

```sh
node --version
git --version
npm --version
```

## 安装、构建与运行

在 **Concordia 源码目录**执行:

```sh
npm install
npm run build
npm test
```

构建产生:

- `dist/src/index.js`:本机 stdio MCP 服务;
- `dist/src/relay.js`:运行在 ZCode/Git 主机上的 Redis relay coordinator;
- `dist/src/codex-waker.js`:运行在 Codex 主机上的事件监听与 App Server 唤醒进程;
- `dist/src/zcode-waker.js`:运行在 ZCode 主机上的事件监听与 ZCode CLI 唤醒进程;
- `zcode-plugin/dist/index.mjs`:随 ZCode 本地插件分发的单文件 bundle。

服务使用 stdio,通常由 MCP 客户端启动。以下命令只用于验证进程可启动,随后会等待标准输入上的 MCP 客户端:

```sh
CONCORDIA_ROOTS=/absolute/path/to/example-app \
CONCORDIA_AGENT_ID=codex \
npm start
```

MCP 协议仅写 stdout;启动与错误日志写 stderr。默认数据库为启动目录的 `.concordia/state.db`。实际同时使用 Codex 和 ZCode 时,应配置同一个绝对 `CONCORDIA_DB`,避免不同 `cwd` 产生两个状态库。

若希望 Codex 在 ZCode 提交、提问或失败时自动恢复审查任务,而不是让模型持续调用 `wait_events`,启动可选事件唤醒器:

```sh
CONCORDIA_TRANSPORT=stdio \
CONCORDIA_ROOTS=/absolute/path/to/example-app \
CONCORDIA_DB=/absolute/path/to/example-app/.concordia/state.db \
CONCORDIA_WAKER_DB=/absolute/path/to/example-app/.concordia/waker.db \
npm run start:waker
```

waker 空闲时只运行 Node.js 事件循环,不调用模型。完整配置、跨机器路径规则、可靠性语义和故障排查见 [Codex 事件唤醒器指南](docs/codex-waker.md)。
可复制的环境变量起点见 [.env.waker.example](.env.waker.example)。

同样地,如需让 ZCode 在新任务、Codex 回答或要求返工时恢复实施会话,而不依赖会话内反复调用 `wait_events`,在 ZCode CLI 所在机器启动:

```sh
CONCORDIA_TRANSPORT=stdio \
CONCORDIA_ROOTS=/absolute/path/to/example-app \
CONCORDIA_DB=/absolute/path/to/example-app/.concordia/state.db \
CONCORDIA_ZCODE_WAKER_DB=/absolute/path/to/example-app/.concordia/zcode-waker.db \
npm run start:zcode-waker
```

`zcode-waker` 空闲时也不会调用模型。它先按事件 `taskId` 精确领取并把 CLI 目录绑定到返回的 worktree,再用 `--prompt --json --surface terminal --mode build` 创建会话;后续事件用 `--resume sess_*` 恢复同一任务会话。Concordia 不调用 Computer Use,也不干预 ZCode agent 自身的工具选择;CLI 子进程不继承 waker 的 relay/API 凭据。详见 [ZCode 事件唤醒器指南](docs/zcode-waker.md),可复制配置见 [.env.zcode-waker.example](.env.zcode-waker.example)。

跨机器协调器使用:

```sh
CONCORDIA_ROOTS=/absolute/path/to/repositories \
CONCORDIA_DB=/absolute/path/to/state.db \
CONCORDIA_REDIS_URL='rediss://user:password@redis.example.com:6379/0' \
CONCORDIA_RELAY_NAMESPACE='team-a' \
CONCORDIA_RELAY_CODEX_TOKEN='<至少 32 字符的随机 token>' \
CONCORDIA_RELAY_ZCODE_TOKEN='<另一个至少 32 字符的随机 token>' \
npm run start:relay
```

协调器不监听入站 HTTP 端口,只主动连接 Redis。跨网络使用 `rediss://`,完整配置见 [跨机器协调指南](docs/remote-coordination.md)。

## 配置

| 变量 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `CONCORDIA_TRANSPORT` | 否 | `stdio` | MCP 客户端传输模式:`stdio` 直接访问本机状态,`redis` 通过中转。 |
| `CONCORDIA_AGENT_ID` | 是 | 无 | 只允许 `codex` 或 `zcode`;角色不匹配的工具会被拒绝。 |
| `CONCORDIA_ROOTS` | stdio/协调器 | 无 | 允许的目标项目根目录;Redis client 不设置。 |
| `CONCORDIA_DB` | 否 | `<cwd>/.concordia/state.db` | stdio/协调器使用的本机 SQLite;Redis client 不设置,绝不能跨机器共享。 |
| `CONCORDIA_REDIS_URL` | Redis 模式 | 无 | Redis URL;远程默认要求 `rediss://`。 |
| `CONCORDIA_RELAY_NAMESPACE` | 否 | `concordia` | 隔离不同部署的 Redis 键,1–64 个安全字符。 |
| `CONCORDIA_RELAY_CODEX_TOKEN` | Codex relay/协调器 | 无 | Codex 请求 HMAC token,至少 32 字符。 |
| `CONCORDIA_RELAY_ZCODE_TOKEN` | ZCode relay/协调器 | 无 | ZCode 请求 HMAC token,至少 32 字符且与 Codex token 不同。 |

例如:

```sh
CONCORDIA_ROOTS=/Users/me/src/project-a,/Users/me/src/project-b
```

路径范围字段 `ownedPaths`、`excludedPaths`、`changedFiles` 必须使用相对工作区的 POSIX 路径,例如 `src/api`,不要使用反斜杠。

## 接入 Codex 与 ZCode

下面使用一组固定示例路径。配置时请把它们全部替换为你机器上的真实绝对路径:

| 含义 | 本节示例值 |
| --- | --- |
| Concordia 源码目录 | `/Users/me/tools/concordia` |
| 被管理的目标 Git 仓库 | `/Users/me/src/example-app` |
| 双端共享数据库 | `/Users/me/src/example-app/.concordia/state.db` |

先完成一次构建并确认两个入口文件存在:

```sh
cd /Users/me/tools/concordia
npm install
npm run build

test -f /Users/me/tools/concordia/dist/src/index.js
test -f /Users/me/tools/concordia/zcode-plugin/dist/index.mjs
git -C /Users/me/src/example-app rev-parse --show-toplevel
```

最后一条命令应输出 `/Users/me/src/example-app`。如果输出的是其他目录,应把后续配置中的目标仓库路径改成实际 Git 根目录。

### 在 Codex 中配置

Codex 桌面应用、CLI 和 IDE 扩展在同一 Codex host 上共用 MCP 配置。配置文件可放在全局 `~/.codex/config.toml`,也可放在可信目标项目的 `.codex/config.toml`。以下方式任选一种,详见 [Codex MCP 官方文档](https://developers.openai.com/codex/mcp/)。

#### 方式 A:编辑 `config.toml`(推荐)

将以下内容追加到 `~/.codex/config.toml`。如果只想让服务器在一个项目中可用,则追加到 `/Users/me/src/example-app/.codex/config.toml`:

```toml
[mcp_servers.concordia]
command = "node"
args = ["/Users/me/tools/concordia/dist/src/index.js"]
cwd = "/Users/me/src/example-app"
enabled = true
startup_timeout_sec = 20
# wait_events 最长等待 60 秒,工具超时需略大于 60 秒。
tool_timeout_sec = 70

[mcp_servers.concordia.env]
CONCORDIA_TRANSPORT = "stdio"
CONCORDIA_ROOTS = "/Users/me/src/example-app"
CONCORDIA_DB = "/Users/me/src/example-app/.concordia/state.db"
CONCORDIA_AGENT_ID = "codex"
```

注意:

- `args` 指向 Concordia 源码构建出的 `dist/src/index.js`,不是目标项目中的文件。
- `cwd`、`CONCORDIA_ROOTS` 和 `CONCORDIA_DB` 指向被管理的目标项目。
- `CONCORDIA_AGENT_ID` 在 Codex 端必须是 `codex`。
- 管理多个仓库时,可用逗号或系统路径分隔符连接多个 `CONCORDIA_ROOTS`;每个任务的 `workspace` 仍必须是其中某个 Git 仓库的根目录。

保存后重启 Codex MCP server:桌面应用可进入 **Settings → MCP servers**,找到 `concordia` 后选择 **Restart**;CLI 或 IDE 扩展可重新启动会话。然后在 Codex 会话中输入 `/mcp`,应能看到 `concordia` 及其 8 个工具。

#### 方式 B:使用 Codex CLI 添加

如果本机安装了 Codex CLI,可以运行:

```sh
codex mcp add concordia \
  --env CONCORDIA_TRANSPORT=stdio \
  --env CONCORDIA_ROOTS=/Users/me/src/example-app \
  --env CONCORDIA_DB=/Users/me/src/example-app/.concordia/state.db \
  --env CONCORDIA_AGENT_ID=codex \
  -- node /Users/me/tools/concordia/dist/src/index.js

codex mcp list
```

CLI 写入的也是 Codex MCP 配置。该命令已经显式指定数据库和允许根目录,因此不依赖 MCP 进程从哪个目录启动。如需 `cwd`、超时等精细选项,再按方式 A 编辑生成的 `~/.codex/config.toml`。

#### Codex 端验证

在一个新的 Codex 会话中要求它“调用 Concordia 的 `list_tasks`”。若返回任务列表或空数组,说明连接成功。Codex 端具有读取权限,并可调用 `create_task`、以 `sender: "codex"` 发送 `ANSWER`/`CANCELLED`,以及调用 `review_task`;它不能领取或提交任务。

### 在 ZCode 中配置

推荐安装仓库内置插件,因为它会同时提供 Concordia MCP server 和 `/tasks`、`/task`、`/watch` 命令。也可以只手动添加 MCP server,但手动方式不会安装这些斜杠命令。参见 [ZCode Plugin 文档](https://zcode.z.ai/en/docs/plugin)和 [ZCode MCP 文档](https://zcode.z.ai/en/docs/mcp-services)。

插件安装后的待办查看、任务领取、事件监听和提交操作,详见 [ZCode 使用指南](docs/zcode-usage.md)。

#### 方式 A:安装 Concordia 插件(推荐)

本仓库根目录的 `marketplace.json` 已将 `zcode-plugin/` 声明为可安装插件。操作步骤:

1. 先按前文执行 `npm run build`,确保 `zcode-plugin/dist/index.mjs` 存在。
2. 在 ZCode 中打开**目标项目** `/Users/me/src/example-app`,不要把 Concordia 源码目录当作目标项目打开。
3. 打开 **Settings → Plugins**。如果页面提示先打开 workspace,请先完成上一步。
4. 点击右上角 **Create → Add marketplace**。
5. 本地开发时选择目录 `/Users/me/tools/concordia`;发布 GitHub 后也可以填写 Concordia 仓库 URL。
6. 在 **Personal** 区域找到 `concordia-local` marketplace,再找到 `concordia` 插件,点击 **Install** 并确认已启用。
7. 打开 **Settings → MCP Servers**,在 **Plugin MCP servers** 分组确认 `plugin:concordia:concordia` 已启用。
8. 新建一个 ZCode 会话,输入 `/tasks`。能够返回任务表或“没有匹配任务”即表示插件和 MCP 都已加载。

插件内置的 `zcode-plugin/.mcp.json` 为:

```json
{
  "mcpServers": {
    "concordia": {
      "type": "stdio",
      "command": "node",
      "args": ["${CLAUDE_PLUGIN_ROOT}/dist/index.mjs"],
      "cwd": "${CLAUDE_PROJECT_DIR}",
      "env": {
        "CONCORDIA_TRANSPORT": "stdio",
        "CONCORDIA_ROOTS": "${CLAUDE_PROJECT_DIR}",
        "CONCORDIA_DB": "${CLAUDE_PROJECT_DIR}/.concordia/state.db",
        "CONCORDIA_AGENT_ID": "zcode"
      },
      "enabled": true,
      "timeoutMs": 70000
    }
  }
}
```

其中:

- `${CLAUDE_PLUGIN_ROOT}`(也可写成 `${ZCODE_PLUGIN_ROOT}`)由 ZCode 替换为已安装插件目录。
- `${CLAUDE_PROJECT_DIR}` 由 ZCode 替换为当前打开的目标项目根目录。
- `CONCORDIA_DB` 会展开为 `/Users/me/src/example-app/.concordia/state.db`;前面的 Codex 配置必须指向完全相同的文件。
- `timeoutMs` 略大于 `wait_events` 允许的最长 60 秒等待,避免正常长轮询被客户端提前中止。

修改 Concordia 或插件源码后,应重新运行 `npm run build`,然后在 ZCode 的 Marketplace sources 中刷新 `concordia-local`。如果插件已经被复制进 ZCode 缓存而非直接引用源码,刷新或重新安装后再开新会话。

#### 方式 B:只手动添加 MCP server

不需要斜杠命令时,可在 ZCode 中打开 **Settings → MCP Servers → New MCP Server**,选择 **Workspace** scope,切换到 **Full configuration**,粘贴以下 JSON:

```json
{
  "mcpServers": {
    "concordia": {
      "type": "stdio",
      "command": "node",
      "args": ["/Users/me/tools/concordia/zcode-plugin/dist/index.mjs"],
      "cwd": "/Users/me/src/example-app",
      "env": {
        "CONCORDIA_TRANSPORT": "stdio",
        "CONCORDIA_ROOTS": "/Users/me/src/example-app",
        "CONCORDIA_DB": "/Users/me/src/example-app/.concordia/state.db",
        "CONCORDIA_AGENT_ID": "zcode"
      },
      "enabled": true,
      "timeoutMs": 70000
    }
  }
}
```

也可以手动写入目标项目的 `/Users/me/src/example-app/.zcode/config.json`:

```json
{
  "mcp": {
    "servers": {
      "concordia": {
        "command": "node",
        "args": ["/Users/me/tools/concordia/zcode-plugin/dist/index.mjs"],
        "cwd": "/Users/me/src/example-app",
        "env": {
          "CONCORDIA_TRANSPORT": "stdio",
          "CONCORDIA_ROOTS": "/Users/me/src/example-app",
          "CONCORDIA_DB": "/Users/me/src/example-app/.concordia/state.db",
          "CONCORDIA_AGENT_ID": "zcode"
        },
        "enable": true
      }
    }
  }
}
```

ZCode 的用户级配置位于 `~/.zcode/cli/config.json`,项目级配置位于 `<project>/.zcode/config.json`。本工具建议使用项目级配置,以免一个固定 `cwd` 和数据库路径意外应用到所有项目。保存后在 MCP 列表确认服务器已启用,并新建会话测试 `list_tasks`。

#### 双端联通验证

1. 分别启动或重启 Codex 与 ZCode 中的 `concordia` MCP server。
2. 在 Codex 中调用 `create_task` 创建一个目标仓库为 `/Users/me/src/example-app` 的任务。
3. 在 ZCode 中运行 `/tasks`,或要求 ZCode 调用 `list_tasks`。
4. 如果 ZCode 能看到刚创建的任务,说明两端正在使用同一数据库。
5. 若看不到,优先核对两端 `CONCORDIA_DB` 的绝对路径是否逐字符一致,再检查两端 `CONCORDIA_ROOTS` 是否包含目标 Git 根目录。

插件提供的 `/tasks`、`/task <task-id>`、`/watch <task-id>` 是只读辅助命令;真正的领取、提交和审核仍由 MCP 工具完成。

### 切换为跨机器 Redis relay

只有需要跨设备时才设置 `CONCORDIA_TRANSPORT=redis`。远端 Codex 的最小配置为:

```toml
[mcp_servers.concordia]
command = "node"
args = ["/Users/me/tools/concordia/dist/src/index.js"]
enabled = true
startup_timeout_sec = 20
tool_timeout_sec = 80

[mcp_servers.concordia.env]
CONCORDIA_AGENT_ID = "codex"
CONCORDIA_TRANSPORT = "redis"
CONCORDIA_REDIS_URL = "rediss://user:password@redis.example.com:6379/0"
CONCORDIA_RELAY_NAMESPACE = "team-a"
CONCORDIA_RELAY_CODEX_TOKEN = "<至少 32 字符的 Codex token>"
```

ZCode/Git 机器需要另外运行 `npm run start:relay`。ZCode MCP 可设置同一个 Redis URL 与 namespace、使用独立的 `CONCORDIA_RELAY_ZCODE_TOKEN`;也可在协调主机继续用默认 `stdio`,直接连接协调器所用的本机 SQLite。Redis 模式下,任务 `workspace` 一律填写 ZCode/Git 机器上的绝对路径。

可直接复制的 ZCode JSON、协调器命令、Redis ACL/TLS 要求和联通步骤见 [跨机器协调指南](docs/remote-coordination.md)。

## MCP 工具

工具返回 JSON 内容及结构化结果。业务错误统一形如 `{ code, message, retryable, details? }`。每次写操作都需唯一 `idempotencyKey`(最长 256 字符);完全相同的重试返回原结果,不重复写入。

| 工具 | 角色 | 作用与关键输入 |
| --- | --- | --- |
| `create_task` | Codex | 创建并发布 `READY` 任务。输入 `spec` 和 `idempotencyKey`。 |
| `claim_task` | ZCode | 原子领取任务;可按 `taskId` 精确领取事件对应任务,或按 `workspace` 筛选最早匹配任务;`leaseSeconds` 为 1–3600(默认 60)。无任务返回 `{ task: null }`;成功包含 `leaseToken`。 |
| `get_task` | 两者 | 获取任务契约、状态、租约摘要、交付物、提交记录与近期事件;`eventLimit` 默认 20、最大 100。 |
| `list_tasks` | 两者 | 以 `status`、`assignee`、`workspace`、`updatedAfter`、`limit` 筛选;默认 20、最大 100。 |
| `send_event` | 两者 | 追加授权事件;可带 `expectedVersion`。ZCode 的写入必须带当前 `leaseToken`。 |
| `wait_events` | 两者 | 查询 `afterEventId` 后的新事件,最长等待 60 秒(默认 30 秒);超时返回空数组。 |
| `submit_task` | ZCode | 提交当前 attempt worktree 的 HEAD commit、精确变更列表、检查、风险和摘要,转入 `REVIEW`。 |
| `review_task` | Codex | 对 `REVIEW` 任务 `approve` 或 `request_changes`;返工至少需要一条 finding。 |

`send_event` 权限:

| 发送者 | 可发送事件 |
| --- | --- |
| Codex | `ANSWER`、`CANCELLED` |
| ZCode | `PROGRESS`、`QUESTION`、`AGENT_STATUS`、`HEARTBEAT`、`FAILED` |

`TASK_CREATED`、`TASK_CLAIMED`、`COMPLETED`、`CHANGES_REQUESTED`、`APPROVED` 由专用工具生成,不能经 `send_event` 伪造。

### 任务契约

```ts
{
  id: string;                 // 字母数字开头,最多 128 字符,可含 . _ -
  objective: string;
  workspace: string;          // 目标 Git 仓库根目录,不是 Concordia 源码目录
  baseCommit?: string;        // 省略时创建时解析为目标仓库 HEAD 的完整 SHA
  ownedPaths: string[];       // 至少一个相对路径
  excludedPaths?: string[];
  constraints: string[];
  acceptance: string[];       // 至少一项
  deliverables: ("commit" | "changed_files" | "checks" | "risks")[];
  delegation: { mode: "auto" | "disabled"; maxConcurrency: number; maxDepth: 1 };
  timeoutSeconds: number;
}
```

`baseCommit` 在创建时解析为完整 SHA。`timeoutSeconds`、`delegation.mode` 与 `maxConcurrency` 目前是被验证和保存的契约信息,不会由运行时自动杀死、调度或限流代理;`maxDepth` 必须为 `1`。

提交检查只接受以下 `commandId` 白名单:`build`、`format-check`、`git-diff-check`、`lint`、`npm-build`、`npm-lint`、`npm-test`、`npm-typecheck`、`test`、`typecheck`。Concordia 记录检查证据,不会执行 payload 中的 shell 命令。

## leaseToken、attempt 与 worktree

### 租约与 fencing token

`claim_task` 成功结果中的 `leaseToken` 是当前执行权的短期凭据。ZCode **必须**把它带入后续每个 `send_event`(包括 `HEARTBEAT`)及 `submit_task`。token 不会进入 `get_task`、事件、提交记录或日志;不要回显、提交或持久化它。

默认租约 60 秒。长任务应在到期前用 `HEARTBEAT` 续租,并可传 `leaseSeconds`(1–3600)。租约过期后另一个领取者可以接手,服务会颁发新 token;旧 token 被围栏拒绝。`expectedVersion` 是可选的乐观并发控制,适合读—改—写过程。

提交进入 `REVIEW` 时租约立即失效。Codex 要求返工后任务回到 `READY`;ZCode 必须再次调用 `claim_task`,在新 attempt worktree 中继续,并使用新 `leaseToken`。旧 token 永远不能恢复使用。

### 每个 attempt 的独立工作区

每次领取创建或复用该尝试专属路径:

```text
<目标 Git 仓库>/.worktrees/<task-id>-zcode-a<attempt>/
```

对应分支为 `concordia/<task-id>-zcode-a<attempt>`。首次从 `baseCommit` 建立;租约过期后的新 attempt 从上一次 worktree 的已提交 `HEAD` 快照恢复(没有可恢复 worktree 时从 base commit 开始)。旧 attempt 目录不会被新 attempt 重用。

**ZCode 必须修改 `claim_task` 返回的 `task.worktreePath`,而不是目标仓库的原始检出目录。** 提交时服务验证:

1. 目标仓库/worktree 均在允许根目录内,且不存在符号链接逃逸;
2. `baseCommit` 仍解析为原完整 SHA,提交历史从其演进;
3. 提交 SHA 是当前 attempt worktree 的完整、不可变 `HEAD`;
4. `changedFiles` 与 Git diff 精确一致;
5. 最终 diff、所有中间提交及未提交改动仅触及 `ownedPaths`,且不触及 `excludedPaths`。

## 端到端示例

假设目标仓库为 `/absolute/path/to/example-app`,两端均连接 `/absolute/path/to/example-app/.concordia/state.db`。以下 JSON 是 MCP 工具参数,不是 shell 命令。

1. Codex 获取目标仓库基准并调用 `create_task`:

   ```sh
   git -C /absolute/path/to/example-app rev-parse HEAD
   ```

   ```json
   {
     "spec": {
       "id": "docs-api-001",
       "objective": "为 API 客户端补充使用文档",
       "workspace": "/absolute/path/to/example-app",
       "baseCommit": "<完整 SHA>",
       "ownedPaths": ["docs", "README.md"],
       "excludedPaths": [".github"],
       "constraints": ["不要修改运行时代码"],
       "acceptance": ["文档包含安装与 API 示例", "npm test 通过"],
       "deliverables": ["commit", "changed_files", "checks", "risks"],
       "delegation": { "mode": "auto", "maxConcurrency": 1, "maxDepth": 1 },
       "timeoutSeconds": 1800
     },
     "idempotencyKey": "docs-api-001:create:v1"
   }
   ```

2. ZCode 领取并保存返回的 `task.worktreePath` 与 `leaseToken`。由 `zcode-waker` 唤醒时必须传入事件对应的 `taskId`,避免领取另一个 READY 任务:

   ```json
   { "agentId": "zcode", "taskId": "docs-api-001", "workspace": "/absolute/path/to/example-app" }
   ```

3. 在返回的 attempt worktree 工作,并用 token 上报进度:

   ```json
   {
     "taskId": "docs-api-001",
     "sender": "zcode",
     "recipient": "codex",
     "type": "PROGRESS",
     "payload": { "phase": "writing", "summary": "正在编写 API 示例" },
     "idempotencyKey": "docs-api-001:progress:writing",
     "leaseToken": "<领取结果的 leaseToken>"
   }
   ```

   该事件将 `CLAIMED` 变为 `RUNNING`。需要澄清时发送 `QUESTION`;Codex 用 `ANSWER` 回复,任务从 `WAITING_INPUT` 返回 `RUNNING`。长任务发送 `HEARTBEAT` 续租。

4. 在 **返回的** worktree 中完成、验证、提交:

   ```sh
   cd /absolute/path/to/example-app/.worktrees/docs-api-001-zcode-a1
   npm test
   git add docs README.md
   git commit -m "docs: add API client guide"
   git rev-parse HEAD
   ```

5. ZCode 调用 `submit_task`。`commit` 为完整 SHA,`changedFiles` 必须与 `baseCommit..commit` diff 完全一致:

   ```json
   {
     "taskId": "docs-api-001",
     "leaseToken": "<领取结果的 leaseToken>",
     "commit": "<完整提交 SHA>",
     "changedFiles": ["README.md", "docs/api-client.md"],
     "checks": [{ "commandId": "npm-test", "exitCode": 0, "summary": "npm test passed" }],
     "risks": [],
     "summary": "补充 API 客户端安装、认证和调用示例。",
     "idempotencyKey": "docs-api-001:submit:1"
   }
   ```

6. Codex 用 `get_task` 读取交付证据并审查 diff,之后调用 `review_task`:

   ```json
   {
     "taskId": "docs-api-001",
     "decision": "approve",
     "summary": "文档内容和测试证据已核对。",
     "idempotencyKey": "docs-api-001:approve:1"
   }
   ```

   返工时使用 `decision: "request_changes"` 并给出至少一个 `{ path?, line?, severity, message }` finding。任务会回到 `READY`,ZCode 需要重新领取。批准只表示任务协议完成,**不会自动合并** worktree branch;后续合并、cherry-pick 或丢弃由用户或上层协调者决定。

## ZCode 命令

| 命令 | 用途 |
| --- | --- |
| `/tasks [status]` | 列出当前工作区近期任务;可按一个或多个状态过滤,显示 ID、状态、执行者、更新时间和目标。 |
| `/task <task-id>` | 显示任务契约、状态、路径范围、验收条件、租约摘要、交付证据与近期事件。 |
| `/watch <task-id>` | 读取当前事件游标后循环 `wait_events` 显示新事件;终态、用户中止或达到观察限制时停止。 |

这些都是查询/观察命令,不会领取、提交或审核任务。

## 安全模型

Concordia 的边界是“可信用户/团队 + 明确工作区白名单”,不是完整的多租户平台。已实现的约束包括:

- 必须显式声明 `codex` 或 `zcode`;工具和事件发送者均做角色校验;
- `CONCORDIA_ROOTS` 限制目标工作区,且工作区必须为该根内的 Git 仓库根;
- 路径范围拒绝绝对路径、`..`、反斜杠、Git/Concordia 控制目录和越界符号链接;
- worktree 及既有目录会解析真实路径,防止逃离目标仓库;
- SQLite 使用外键、WAL、事务和任务 version 乐观锁;事件 idempotency key 全局唯一;
- `leaseToken` 使用时序安全比较,轮换后旧领取者无法继续写入;
- Redis relay 使用分角色 HMAC-SHA256 签名、时间戳、nonce 防重放、响应 TTL 和单协调器锁;
- 远程 Redis 默认强制 `rediss://`,角色 token 与 Redis 凭据均不进入日志;
- `submit_task` 不执行任意命令;只记录白名单检查 ID 的结果;
- 业务错误不回显环境变量、凭据或完整命令输出。

任何能读写本地数据库、Git 工作区、Redis 数据或 MCP 配置的用户仍在同一信任域。跨机器生产部署应使用 Redis ACL 和 TLS。

## 测试与开发

```sh
npm run typecheck  # 严格 TypeScript 类型检查
npm run build      # 编译服务并打包 ZCode 插件
npm test           # build 后运行 Node 内置测试
```

若本机有测试 Redis,可额外执行真实 relay 往返测试:

```sh
CONCORDIA_TEST_REDIS_URL=redis://127.0.0.1:6379 npm test
```

测试覆盖完整领取—运行—提交—返工重领—批准流程、并发领取、幂等、租约恢复、fencing token、数据库重启持久化、事件等待、两个 waker 的游标与投递恢复、Codex App Server 与 ZCode CLI 生命周期、基准提交验证、worktree 恢复、relay 签名/权限/TLS 校验,以及路径/符号链接/提交历史范围校验。

详见:[系统设计](docs/design.md)、[开发指南](docs/development.md)、[ZCode 使用指南](docs/zcode-usage.md)、[Codex 事件唤醒器指南](docs/codex-waker.md)与 [ZCode 事件唤醒器指南](docs/zcode-waker.md)。

## 目录结构

```text
concordia/                         # Concordia 源码目录
├── src/
│   ├── index.ts                   # stdio MCP server 与 8 个工具
│   ├── protocol.ts                # 类型、验证、错误模型
│   ├── database.ts                # SQLite 初始化、迁移、事务
│   ├── events.ts                  # 事件追加、查询、有界等待
│   ├── tasks.ts                   # 状态机、租约、幂等、交付验证
│   ├── workspace.ts               # Git/worktree、路径范围
│   ├── relay-protocol.ts          # relay 签名信封与安全校验
│   ├── relay-client.ts            # Redis 模式 MCP client
│   ├── relay.ts                   # Redis Streams coordinator
│   ├── codex-app-server.ts        # Codex App Server JSONL 客户端
│   ├── zcode-cli.ts               # ZCode headless CLI 客户端
│   ├── waker-source.ts            # SQLite/Redis 共享事件源
│   ├── codex-waker.ts             # 事件过滤、唤醒与重试循环
│   ├── zcode-waker.ts             # ZCode CLI 事件唤醒与重试循环
│   └── waker-state.ts             # 独立游标、线程和投递状态库
├── tests/                         # 核心、relay、CLI 与 waker 测试
├── marketplace.json               # ZCode 本地/GitHub marketplace 入口
├── zcode-plugin/                  # 可加载的 ZCode 本地插件
│   ├── .mcp.json
│   ├── .zcode-plugin/plugin.json
│   └── commands/                  # /tasks、/task、/watch
├── docs/
│   ├── zcode-usage.md            # ZCode 待办、监听与执行指南
│   ├── codex-waker.md            # Codex 事件驱动唤醒与部署
│   ├── zcode-waker.md            # ZCode 事件驱动唤醒与部署
│   ├── design.md
│   └── development.md
├── package.json
├── .env.waker.example             # Codex waker 环境变量模板
├── .env.zcode-waker.example       # ZCode waker 环境变量模板
└── tsconfig.json

<目标 Git 仓库>/
└── .worktrees/<task>-zcode-a<attempt>/  # Concordia 运行时创建
```

本仓库 `.gitignore` 忽略 `node_modules/`、`dist/`、`.concordia/`、`.worktrees/` 和 TypeScript 构建缓存。

## 开源协议

Concordia 使用 [MIT License](LICENSE) 开源。你可以自由使用、复制、修改、合并、发布和分发本软件,但必须保留原始版权与许可声明。本软件按“原样”提供,不附带任何明示或默示担保。

## 故障排查

| 现象 | 处理 |
| --- | --- |
| `CONCORDIA_AGENT_ID is required` | 为 MCP server 设置 `CONCORDIA_AGENT_ID=codex` 或 `zcode`。 |
| `CONCORDIA_ROOTS must contain at least one allowed root` | 配置至少一个存在的目标项目根目录。 |
| `WORKSPACE_DENIED` | 确认 `workspace` 是位于 `CONCORDIA_ROOTS` 内的目标 Git 仓库根,而不是其子目录或 Concordia 源码目录;检查软链接。 |
| 两端看不到彼此任务 | 两端 `CONCORDIA_DB` 必须是同一个绝对文件。若 ZCode 使用默认值,Codex 应设为 `<目标仓库>/.concordia/state.db`。 |
| `Redis relay request timed out` | 确认协调器运行中,Redis URL、数据库编号和 namespace 一致,ACL 允许所需命令。 |
| 远程 `redis://` 被拒绝 | 生产环境改用 `rediss://`;只有可信开发网络才设置 `CONCORDIA_RELAY_ALLOW_INSECURE=true`。 |
| `claim_task` 返回 `task: null` | 没有匹配的 `READY` 任务,也没有过期可恢复任务;用 `list_tasks` 查看。 |
| `LEASE_CONFLICT` | token 不正确、租约过期或已被重领。重新领取,切勿复用旧 token。 |
| `STALE_VERSION` | 读取后任务被其他写操作改变;重新 `get_task` 后继续。 |
| `BASE_COMMIT_MISMATCH` | 基准 SHA 无法解析或尝试历史不从其演进;检查 Git 历史和任务 `baseCommit`。 |
| `PATH_SCOPE_VIOLATION` / 提交被拒绝 | 核对 `ownedPaths`/`excludedPaths`;最终 diff、中间提交与未提交改动必须全部在允许范围内。 |
| 提交不是当前 HEAD | 在当前 `task.worktreePath` 中提交,并传 `git rev-parse HEAD` 的完整 SHA。 |
| SQLite 初始化被锁定 | 稍后重试;初始化有有限重试。若持续发生,检查异常进程是否占用同一数据库。 |
| `waker.event_failed` 反复出现 | 检查 Codex CLI 登录、App Server、Codex 端 Concordia MCP 与 `waker.db` 中的 `last_error`。 |

## 当前限制

- 当前版本为 `0.4.0`,`package.json` 标为 `private: true`,尚未作为 npm 包发布。
- Node 的 `node:sqlite` 在部分 Node 22 发行版可能显示实验性 API 警告;采用前请按自身 Node 策略评估。
- Redis relay 当前只支持单活动协调器,不提供多协调器高可用、远程备份或自动清理旧 attempt worktree。
- `timeoutSeconds`、`delegation.maxConcurrency`、`delegation.mode` 不会被运行时强制调度或限流。
- `wait_events` 是最多 60 秒的轮询等待;交互会话需自行维护游标。可选 `codex-waker` 与 `zcode-waker` 能在模型外持久监听并按事件启动或恢复对应代理 turn。
- 提交路径校验不替代人工/自动代码审查、CI、合并策略和发布流程。