Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ topics: ["client-cli", "host-orchestration", "headless-protocol", "issue-135"]
todo_ids: ["CCLI-003", "CCLI-004"]
created: 2026-03-02
updated: 2026-03-02
status: active
status: archived
mode: openspec
---

Expand Down Expand Up @@ -79,3 +79,4 @@ mode: openspec
- Slice B implementation PR: `https://github.com/zts212653/Deterministic-Agent-Runtime-Engine/pull/145`
- Review thread fixed by this follow-up: `https://github.com/zts212653/Deterministic-Agent-Runtime-Engine/pull/145#discussion_r2871270479`
- Related owner comment acknowledged after fix: `https://github.com/zts212653/Deterministic-Agent-Runtime-Engine/pull/145#issuecomment-3983060182`
- Archive target: `openspec/changes/archive/2026-03-02-client-headless-event-envelope-v1/`
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ topics: ["client-cli", "host-orchestration", "headless-protocol", "issue-135"]
todo_ids: ["CCLI-001", "CCLI-002"]
created: 2026-03-02
updated: 2026-03-02
status: active
status: archived
mode: openspec
---

Expand Down Expand Up @@ -60,3 +60,4 @@ mode: openspec
### Review and Merge Gate Links

- Intent PR: `https://github.com/zts212653/Deterministic-Agent-Runtime-Engine/pull/141`
- Archive target: `openspec/changes/archive/2026-03-02-client-host-orchestration-doc-baseline/`
62 changes: 62 additions & 0 deletions docs/features/client-external-control-plane-v1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
change_ids: ["client-external-control-plane-v1"]
doc_kind: feature
topics: ["client-cli", "host-orchestration", "control-plane", "issue-135"]
todo_ids: ["CCLI-005", "CCLI-006"]
created: 2026-03-02
updated: 2026-03-02
status: active
mode: openspec
---

# Feature: client-external-control-plane-v1

## Scope

落实 Issue #135 的 Slice C:为 `client/` 增加外部结构化 control plane v1,优先采用 `--control-stdin` 作为本地宿主入口,并覆盖审批、MCP、skills 与 session status 的最小控制面。

## OpenSpec Artifacts

- Proposal: `openspec/changes/client-external-control-plane-v1/proposal.md`
- Design: `openspec/changes/client-external-control-plane-v1/design.md`
- Specs:
- `openspec/changes/client-external-control-plane-v1/specs/client-host-orchestration/spec.md`
- Tasks: `openspec/changes/client-external-control-plane-v1/tasks.md`

## TODO Coverage

- `CCLI-005`
- `CCLI-006`

## Evidence

### Commands

- `git fetch origin`
- `git worktree add .worktrees/client-external-control-plane-v1 -b codex/client-external-control-plane-v1 origin/main`
- `openspec list`
- `openspec validate client-external-control-plane-v1 --type change --strict --json --no-interactive`
- `./scripts/ci/check_governance_evidence_truth.sh`

### Results

- `git fetch origin`: confirmed `origin/main` includes merged Slice B via PR `#145`.
- `git worktree add .worktrees/client-external-control-plane-v1 -b codex/client-external-control-plane-v1 origin/main`: created an isolated Slice C workspace from `origin/main` commit `bc39bc0`.
- `openspec list`: confirms Slice A / Slice B have been archived out of the active change list, and the new Slice C kickoff change is recognized as `0/7 tasks`.
- `openspec validate client-external-control-plane-v1 --type change --strict --json --no-interactive`: passed (`1/1` change valid, `0` issues).
- `./scripts/ci/check_governance_evidence_truth.sh`: passed after the Slice C feature evidence block and prior-slice archive moves were synchronized.

### Behavior Verification

- Happy path: the planned Slice C contract narrows v1 external control to `--control-stdin`, preserving the landed headless event envelope from Slice B as the read-only observation channel.
- Error branch: Slice C keeps unknown action ids and unsupported MCP operations on the structured error path; it does not permit fallback to prompt text or undocumented CLI-only verbs such as `mcp:unload`.

### Risks and Rollback

- Risk: adding a second stdin consumer beside `chat` / `script` can create framing ambiguity if control and prompt input are not cleanly separated.
- Rollback: drop the Slice C kickoff change and keep the repository at the merged Slice B baseline where headless remains read-only.

### Review and Merge Gate Links

- Slice A intent gate (merged): `https://github.com/zts212653/Deterministic-Agent-Runtime-Engine/pull/141`
- Slice B implementation gate (merged): `https://github.com/zts212653/Deterministic-Agent-Runtime-Engine/pull/145`
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ mode: openspec

## 认领声明(Claim Ledger)

> 当前状态:Slice A 的 docs-only intent PR 已于 2026-03-02 合入 `main`。
> Slice B 的实现与验证已经在 `codex/client-headless-event-envelope-v1` 完成,当前待提交/评审;Slice C/D 尚未开始
> 当前状态:Slice A / Slice B 已于 2026-03-02 完成并合入 `main`,当前进入 Slice C kickoff
> Slice C 负责外部 control plane v1;Slice D 继续承担 capability discovery 与宿主级回归测试

| Claim ID | TODO Scope | Owner | Status | Declared At | Expires At | OpenSpec Change | Notes |
|---|---|---|---|---|---|---|---|
| CLM-20260302-CCLI-A | CCLI-001~CCLI-002 | bouillipx | done | 2026-03-02 | 2026-03-09 | `client-host-orchestration-doc-baseline` | Slice A: docs baseline、OpenSpec artifacts 与 intent PR payload 已经随 PR `#141` 合入 `main`。 |
| CLM-20260302-CCLI-B | CCLI-003~CCLI-004 | bouillipx | active | 2026-03-02 | 2026-03-09 | `client-headless-event-envelope-v1` | Slice B: 显式 headless 模式、versioned event envelope v1、相关测试与执行证据。 |
| CLM-20260302-CCLI-A | CCLI-001~CCLI-002 | bouillipx | done | 2026-03-02 | 2026-03-09 | `client-host-orchestration-doc-baseline` | Slice A: docs baseline 已随 PR `#141` 合入 `main`,待归档到 `openspec/changes/archive/2026-03-02-client-host-orchestration-doc-baseline/`。 |
| CLM-20260302-CCLI-B | CCLI-003~CCLI-004 | bouillipx | done | 2026-03-02 | 2026-03-09 | `client-headless-event-envelope-v1` | Slice B: headless event envelope v1 已随 PR `#145` 合入 `main`,待归档到 `openspec/changes/archive/2026-03-02-client-headless-event-envelope-v1/`。 |
| CLM-20260302-CCLI-C | CCLI-005~CCLI-006 | bouillipx | active | 2026-03-02 | 2026-03-09 | `client-external-control-plane-v1` | Slice C: 建立 `--control-stdin` v1 基线、MCP/approval/status/skills 外部控制 contract 与 docs-only intent PR payload。 |

## 切片规划

Expand All @@ -37,12 +38,12 @@ mode: openspec

| ID | Priority | Status | Gap ID | Planned OpenSpec Change | Task | Owner | Evidence | Last Updated |
|---|---|---|---|---|---|---|---|---|
| CCLI-001 | P0 | done | CCLI-GAP-006 | `client-host-orchestration-doc-baseline` | 更新 `client/DESIGN.md`,新增“宿主编排 / headless / control plane / capability discovery / 错误语义”章节,作为后续实现唯一设计输入。 | bouillipx | `client/DESIGN.md`;`openspec/changes/client-host-orchestration-doc-baseline/design.md` | 2026-03-02 |
| CCLI-002 | P0 | done | CCLI-GAP-006 | `client-host-orchestration-doc-baseline` | 更新 `client/README.md`,明确当前 `--output json` 是 legacy automation schema,补充后续宿主协议模式的兼容说明。 | bouillipx | `client/README.md`;`docs/features/client-host-orchestration-doc-baseline.md` | 2026-03-02 |
| CCLI-003 | P1 | done | CCLI-GAP-001 | `client-headless-event-envelope-v1` | 为 `client` 设计并实现显式 headless 模式,定义禁止 prompt / 禁止内联审批 / 只输出协议帧的行为边界。 | bouillipx | `client/main.py`;`client/session.py`;`tests/integration/test_client_cli_flow.py`;`docs/features/client-headless-event-envelope-v1.md` | 2026-03-02 |
| CCLI-004 | P1 | done | CCLI-GAP-002 | `client-headless-event-envelope-v1` | 设计并实现 versioned event envelope(至少含 `schema_version`、`run_id`、`seq`、`event`、`data`),并定义与现有 JSON 输出的兼容策略。 | bouillipx | `client/render/headless.py`;`tests/unit/test_client_cli.py`;`tests/integration/test_client_cli_flow.py`;`docs/features/client-headless-event-envelope-v1.md` | 2026-03-02 |
| CCLI-005 | P1 | todo | CCLI-GAP-003 | `client-external-control-plane-v1` | 设计外部控制协议入口(如 `control-stdin` 或 loopback RPC),覆盖 approvals / MCP / skills / status 的结构化控制。 | TBD | `client/main.py`;`client/runtime/action_client.py`;相关 OpenSpec design/specs/tasks | 2026-03-02 |
| CCLI-006 | P2 | todo | CCLI-GAP-005 | `client-external-control-plane-v1` | 将当前 canonical MCP actions(首批为 `mcp:list/reload/show-tool`)接入外部 control plane,并明确运行中生效与错误处理语义。CLI 层 `unload` 待后续补 canonical action 后再纳入宿主协议面。 | TBD | `client/commands/mcp.py`;相关集成测试 | 2026-03-02 |
| CCLI-001 | P0 | done | CCLI-GAP-006 | `client-host-orchestration-doc-baseline` | 更新 `client/DESIGN.md`,新增“宿主编排 / headless / control plane / capability discovery / 错误语义”章节,作为后续实现唯一设计输入。 | bouillipx | `client/DESIGN.md`;`openspec/changes/archive/2026-03-02-client-host-orchestration-doc-baseline/` | 2026-03-02 |
| CCLI-002 | P0 | done | CCLI-GAP-006 | `client-host-orchestration-doc-baseline` | 更新 `client/README.md`,明确当前 `--output json` 是 legacy automation schema,补充后续宿主协议模式的兼容说明。 | bouillipx | `client/README.md`;`docs/features/archive/client-host-orchestration-doc-baseline.md` | 2026-03-02 |
| CCLI-003 | P1 | done | CCLI-GAP-001 | `client-headless-event-envelope-v1` | 为 `client` 设计并实现显式 headless 模式,定义禁止 prompt / 禁止内联审批 / 只输出协议帧的行为边界。 | bouillipx | `client/main.py`;`client/session.py`;`tests/integration/test_client_cli_flow.py`;`docs/features/archive/client-headless-event-envelope-v1.md` | 2026-03-02 |
| CCLI-004 | P1 | done | CCLI-GAP-002 | `client-headless-event-envelope-v1` | 设计并实现 versioned event envelope(至少含 `schema_version`、`run_id`、`seq`、`event`、`data`),并定义与现有 JSON 输出的兼容策略。 | bouillipx | `client/render/headless.py`;`tests/unit/test_client_cli.py`;`tests/integration/test_client_cli_flow.py`;`docs/features/archive/client-headless-event-envelope-v1.md` | 2026-03-02 |
| CCLI-005 | P1 | todo | CCLI-GAP-003 | `client-external-control-plane-v1` | 设计外部控制协议入口(如 `control-stdin` 或 loopback RPC),覆盖 approvals / MCP / skills / status 的结构化控制。 | bouillipx | `client/main.py`;`client/runtime/action_client.py`;相关 OpenSpec design/specs/tasks | 2026-03-02 |
| CCLI-006 | P2 | todo | CCLI-GAP-005 | `client-external-control-plane-v1` | 将当前 canonical MCP actions(首批为 `mcp:list/reload/show-tool`)接入外部 control plane,并明确运行中生效与错误处理语义。CLI 层 `unload` 待后续补 canonical action 后再纳入宿主协议面。 | bouillipx | `client/commands/mcp.py`;相关集成测试 | 2026-03-02 |
| CCLI-007 | P2 | todo | CCLI-GAP-004 | `client-capability-discovery-and-host-tests` | 将 `actions:list` 提升到 CLI 宿主协议面,并定义启动握手或显式查询命令。 | TBD | `dare_framework/transport/interaction/resource_action.py`;`client/main.py`;相关文档 | 2026-03-02 |
| CCLI-008 | P1 | todo | CCLI-GAP-006 | `client-capability-discovery-and-host-tests` | 新增 headless 协议稳定性、外部控制、能力发现三组集成测试,并回写 README / 设计文档中的验证锚点。 | TBD | `tests/integration/test_client_cli_flow.py`;新增协议测试文件 | 2026-03-02 |

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-03-02
98 changes: 98 additions & 0 deletions openspec/changes/client-external-control-plane-v1/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
## Context

`client/` 现在已经具备宿主可读的 headless 事件面,但控制仍然留在进程内:

- CLI slash 命令直接调用本地 handler;
- `TransportActionClient` 直接通过 `DirectClientChannel.ask(...)` 进入 action/control;
- 宿主无法在同一 headless run 外部发送结构化审批或 MCP 请求。

Issue #135 的 Slice C 需要补足“写路径”,但不应同时引入网络暴露面或新命名体系。因此 v1 设计收敛到:

1. 控制输入来自 `--control-stdin`;
2. 动作 id 继续复用 canonical `resource:action`;
3. CLI 只做协议桥接,不重写运行时 action 语义。

## Goals / Non-Goals

**Goals:**

- 为 headless `run/script` 提供可选的 `--control-stdin` 本地控制入口。
- 定义最小 command/result/error envelope 和请求关联规则。
- 覆盖 approvals、MCP、skills 和 status 的最小宿主控制面。
- 对未知 action、非法参数、运行时失败给出结构化错误。

**Non-Goals:**

- 本次不实现 loopback RPC 或远程访问。
- 本次不实现 capability discovery 握手;`actions:list` 留给 Slice D。
- 本次不把 CLI-only `mcp unload` 暴露为宿主协议动作。
- 本次不改变 Slice B 已落地的 headless event envelope 格式。

## Decisions

### Decision 1: v1 只支持 `--control-stdin`

- 宿主在启动 headless `run/script` 时显式打开 `--control-stdin`。
- stdin 按“一行一个 JSON command frame”解析,不与 `chat` prompt 复用。
- 未启用 `--control-stdin` 时,headless 保持 Slice B 的只读事件语义。

### Decision 2: command/result/error envelope 与 headless event envelope 分离

- command frame 顶层字段至少包含:
- `schema_version`
- `id`
- `action`
- `params`
- result frame 顶层字段至少包含:
- `schema_version`
- `id`
- `ok`
- `result`
- `error`
- 结果帧不复用 `event` 字段,避免把控制往返混入只读事件流语义。

### Decision 3: v1 action 范围收敛到现有 canonical surface

- 首批桥接 action:
- `approvals:list`
- `approvals:poll`
- `approvals:grant`
- `approvals:deny`
- `approvals:revoke`
- `mcp:list`
- `mcp:reload`
- `mcp:show-tool`
- `skills:list`
- `status:get`
- `status:get` 由 CLI session state 提供结构化快照;其余 action 复用现有运行时 handler。
- `mcp:unload` 继续留在 CLI 命令面,不进入 v1 协议基线。

### Decision 4: 错误必须结构化且不可回落为 prompt UX

- JSON 解析失败、未知 action、缺失参数、运行时 handler 失败都返回 `ok=false` 的结构化 error。
- 启用了 `--control-stdin` 的 headless 会话不得把控制错误打印成仅人类可读的交互提示。
- 审批已超时或 session 已结束时,control 响应仍需给出确定性错误码。

## Risks / Trade-offs

- [Risk] 同一个进程同时消费 task stdin 和 control stdin 会产生边界混淆。
→ Mitigation: v1 仅允许 `run/script` 使用 `--control-stdin`,不让 `chat` 混入这条路径。

- [Risk] `status:get` 不是现有 runtime canonical action。
→ Mitigation: 明确它是 CLI 宿主协议层动作,由 session state 投影,不伪装成 transport runtime action。

- [Risk] MCP 命令面当前支持 `unload`,但 canonical action 不支持。
→ Mitigation: v1 只桥接 `mcp:list/reload/show-tool`,把 `unload` 明确留给后续 canonicalization。

## Migration Plan

1. 在 CLI 参数层增加 `--control-stdin`,限定其只作用于 headless `run/script`。
2. 增加 control frame 解析/分发/响应逻辑,并桥接现有 approvals、skills、MCP 和 status handler。
3. 为 happy path、unknown action、handler failure、session-lifecycle edge case 增加测试。
4. 回写 feature evidence、TODO ledger 与 OpenSpec task 状态。

## Open Questions

- control result/error 是否输出到 stdout 还是 stderr,才能兼容宿主同时读取事件流与控制响应?
- `status:get` 的最小返回字段是否应包含 `mode/status/running/active_task/pending_approvals`?
- `mcp:show-tool` 与现有 `/mcp inspect` 的输出投影是否需要完全一致,还是先只保证结构化字段稳定?
39 changes: 39 additions & 0 deletions openspec/changes/client-external-control-plane-v1/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
## Why

Slice B 已经把 `client/` 推进到显式 headless + versioned event envelope v1,但宿主仍然只能“看”事件,不能对活跃 run 发回结构化控制。Issue #135 的下一段缺口因此不在事件读取,而在本地 control plane。

当前 `main` 的阻塞点有两类:

1. 审批、MCP、skills 与 session status 仍主要暴露为 CLI 内部 slash 命令或 `TransportActionClient` 直接调用,宿主没有外部稳定入口。
2. `client/` 还没有定义控制命令帧、结果帧、错误帧和运行中生效语义,导致 headless 会话只能 fail-fast,不能被宿主接管。

Slice C 先收敛到 `--control-stdin` 这一条本地控制面:避免提前引入 loopback 端口和身份边界,同时复用已有 canonical action ids 与现有运行时语义。

## What Changes

- 为 `client run` / `client script` 的 headless 会话引入 `--control-stdin` 控制面入口。
- 定义 control command/result/error envelope v1,明确 `schema_version`、请求关联 `id`、`action`、`params`、`ok`、`result`、`error` 字段。
- 将 approvals、MCP、skills 与 status 的最小控制面桥接到外部宿主协议。
- 为控制往返、错误路径和运行中生效语义增加测试与执行证据。

## Capabilities

### Modified Capabilities

- `client-host-orchestration`: 把“planned control plane”推进为首个 landed 外部控制入口。
- `transport-channel`: 复用 canonical `resource:action` 语义作为 CLI 控制桥接目标,而不是引入新的 dotted 命名。

## Impact

- 影响文件:
- `client/main.py`
- `client/runtime/action_client.py`
- `client/commands/mcp.py`
- `tests/unit/test_client_cli.py`
- `tests/integration/test_client_cli_flow.py`
- `docs/features/client-external-control-plane-v1.md`
- `openspec/changes/client-external-control-plane-v1/**`
- 不包含:
- loopback RPC / `--control-port`
- capability discovery 握手
- `mcp:unload` canonicalization
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## MODIFIED Requirements

### Requirement: Host control is provided through a structured local control plane
The system SHALL provide a structured local control plane for host-orchestrated sessions.

- v1 MUST support a local `--control-stdin` entry for headless `run/script`.
- The control plane MUST accept deterministic actions for approvals, MCP management, skills discovery, and session status.
- Control responses MUST use structured success/error payloads with request correlation.
- The control plane MUST preserve existing approval and action semantics rather than bypassing them.

#### Scenario: Host grants approval through structured control
- **GIVEN** a running headless session emits an approval-pending event
- **WHEN** the host submits a structured approval grant command
- **THEN** the client resolves the approval through the deterministic approval action path
- **AND** the result is returned as a structured control response

#### Scenario: Unknown control action fails structurally
- **GIVEN** a host sends a control command with an unsupported action id
- **WHEN** the client validates the command frame
- **THEN** the client returns a structured error response
- **AND** it does not fall back to prompt-oriented or natural-language command parsing

## ADDED Requirements

### Requirement: Control plane v1 reuses canonical action identifiers
The system SHALL expose control-plane actions using canonical stable identifiers.

- MCP control in v1 MUST be limited to `mcp:list`, `mcp:reload`, and `mcp:show-tool`.
- CLI-only verbs without canonical actions, including `mcp:unload`, MUST NOT be advertised as v1 host protocol actions.
- Approvals and skills actions MUST reuse their existing `resource:action` identifiers.

#### Scenario: Host reloads MCP providers through canonical action id
- **GIVEN** the host needs to refresh MCP provider paths for a running headless session
- **WHEN** it sends action `mcp:reload` through the control plane
- **THEN** the client reuses the current MCP reload runtime path
- **AND** the response is returned as a structured control result

#### Scenario: Host requests CLI session status
- **GIVEN** a headless session is running under the CLI host protocol
- **WHEN** the host sends action `status:get`
- **THEN** the client returns a structured snapshot of current session state
- **AND** the response is correlated to the request id
Loading