From 9638d5e7a9e7f9939eda118540836fb4db54be23 Mon Sep 17 00:00:00 2001 From: suzuke Date: Mon, 27 Apr 2026 13:03:48 +0800 Subject: [PATCH 1/3] =?UTF-8?q?docs(migration):=20add=20Sprint=202=20Phase?= =?UTF-8?q?=20C=20=E2=80=94=20Why=20migrate=20+=20Migration=20steps=20+=20?= =?UTF-8?q?Known=20incompatibilities?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase C completes the migration guide. The 7-section skeleton from Phase A now has full content in every section: Phase A wrote §2 (CLI flag mapping) + §3 (fleet.yaml schema diff); Phase B wrote §4 (backend invocation diff) + §5 (MCP tool API diff); Phase A follow-up (#65) extended §3 with outbound_capabilities; this Phase C lands §1 (Why migrate?) + §6 (Migration steps) + §7 (Known incompatibilities and deferred parity). §1 Why migrate? — operator-direct, no marketing pitch. - "What you gain": native PTY mux, cross-platform (macOS/Linux/Windows), type safety where it matters, async daemon, single source-of-truth backend table, built-in TUI. - "Why @suzuke/agend is being deprecated": JS Number precision, tmux multiplexer constraints, process management overhead, implicit channel ACLs. - "Should you migrate now?": triage between immediate vs defer based on fleet size, OS coverage, TUI dependency, cost-guard usage. - "Pre-alpha caveat": pin version, read release notes, keep backup — cross-links to §6 rollback procedure and forward to release-notes cadence. §6 Migration steps — 7-stage actionable plan. 1. Pre-migration audit (TS state inventory: ls/topic/access/schedule listings, fleet.yaml read-through, AGENTS.md/GEMINI.md gitignore audit). 2. Snapshot data (fleet.yaml + $AGEND_HOME tar + version string). 3. Choose mode (greenfield vs in-place); recommend greenfield unless meaningful historic state. 4. Workflow change: git worktree per branch (encoded in Rust resume strategy + global CLAUDE.md operator policy). 5. Cross-link integrate (apply §2/§3/§4/§5 mappings to fleet.yaml, prompts, runbooks). 6. Post-migration smoke test checklist (8 items covering daemon start, bot reply, allowlist gate, cross-instance dispatch, set_waiting_on round-trip, cost-guard pre-check, CI watch loop, config reload). 7. Rollback path (stop, restore $AGEND_HOME backup, reinstall pinned version, file agend-terminal issue with daemon.log + redacted fleet.yaml + version). Keep backup ≥ 30 days post-cutover. §7 Known incompatibilities and deferred parity — explicit risk register. - TS-only commands removed (recap from §2): init, restart, reload, logs, fleet history/activity/cleanup, backend trust, topic, access, schedule, update, install/uninstall, web, export/import, export-chat, health. - TS-only fleet.yaml fields removed (recap from §3): 14 InstanceConfig fields + 4 top-level keys (project_roots, profiles, health_port, stt). - TS-only access semantics: pairing-mode users must enumerate to channel.user_allowlist (Phase A §3.1 referenced). - Pending parity (Rust roadmap): outbound_capabilities Sprint 23 hard parse error promotion, capability matrix pending markers, AGEND_TOOL_SET profile mechanism, cross-channel architecture (Discord/Slack via auth.rs::gate_outbound_for_agent), list_instances tags filter parity. - Functional limitations during transition: cost_guard not yet per-instance on Rust, Discord un-quote requirement, pairing-mode enumeration. - Operator caveats: user_allowlist DEBUG-not-WARN log level, pre-alpha schema instability, no archive format, tmux not used on Rust. All cross-links use the established stable anchors: {#why-migrate} {#cli-flag-mapping} {#fleet-yaml-schema-diff} {#backend-invocation-diff} {#mcp-tool-api-diff} {#migration-steps} {#known-incompatibilities}. zh-TW pair mirrors EN with body in 繁體中文; technical keywords, file paths, GitHub URLs, anchor IDs preserved in English. Stats will report after rebase. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/migration-to-agend-terminal.md | 196 +++++++++++++++++++++- docs/migration-to-agend-terminal.zh-TW.md | 196 +++++++++++++++++++++- 2 files changed, 386 insertions(+), 6 deletions(-) diff --git a/docs/migration-to-agend-terminal.md b/docs/migration-to-agend-terminal.md index b6f2b111..67be9f08 100644 --- a/docs/migration-to-agend-terminal.md +++ b/docs/migration-to-agend-terminal.md @@ -16,7 +16,48 @@ ## Why migrate? {#why-migrate} -*(Phase C — covers: motivation, agend-terminal feature delta over @suzuke/agend, when not to migrate, supported migration window.)* +`@suzuke/agend` is in maintenance mode. New features land in `agend-terminal`. This section is the honest version of the value-prop and the cost — direct, not marketing. + +### What you gain + +- **Native PTY multiplexing.** The Rust daemon talks to PTYs directly (`openpty` on Unix, `ConPTY` on Windows). `@suzuke/agend` shells out to `tmux new-window` for every backend launch and inherits every tmux gotcha (server crashes, stale window IDs, attach quirks). On Rust, the daemon's own TUI is the multiplexer. +- **Cross-platform support.** macOS / Linux / Windows on Rust; macOS / Linux only on TS (`tmux` does not run natively on Windows). `which::which` honours `PATHEXT` so `claude.cmd` / `codex.ps1` resolve correctly on Windows. +- **Type safety where it matters.** The migration items in [§3 fleet.yaml schema diff](#fleet-yaml-schema-diff) — `group_id` precision, `topic_id` width, `outbound_capabilities` enum closure — are checked at config load on Rust. The TS daemon learned the same lessons through bug reports. +- **Async daemon, no per-instance Node process.** Rust spawns each agent as a child process under one daemon binary. The TS daemon is a single Node process but the runtime overhead per instance is materially higher than a Rust task; heavy fleets (>5 simultaneous instances) feel this most. +- **One source-of-truth backend table.** `BackendPreset` centralises every backend's spawn flags / resume mode / instructions delivery. On TS, the same surface is split across five `CliBackend` classes, and per-backend behaviour drifts. See [§4 Backend invocation diff](#backend-invocation-diff) for the side-by-side. +- **A built-in TUI.** `agend-terminal app` gives you a multi-pane terminal app over the running daemon. `@suzuke/agend` has only the web UI (`agend web`); the TUI is a Rust-only feature. + +### Why `@suzuke/agend` is being deprecated + +- **JS `Number.MAX_SAFE_INTEGER`** (2^53 − 1) bites the moment you use Discord guild IDs without quoting; Telegram supergroup IDs sit safely below the threshold but the workaround discipline ("always quote large IDs") was inconsistent across the codebase. Rust's `i64` covers both with bare-int form — see [High-friction #2](#fleet-yaml-schema-diff). +- **`tmux` as the multiplexer** locks the daemon out of Windows entirely and adds a layer where bugs hide ("the pane went dark; was it the agent, the tmux server, or the wrapper script?"). +- **Process management overhead.** Each spawned backend lives inside a tmux pane managed via signal-capturing wrapper scripts and PTY-output regex on TS. Rust drives the PTY directly. +- **Implicit channel ACLs.** TS treats every outbound MCP call as universally available to every instance. Rust's PR #230 introduced an explicit `outbound_capabilities` allowlist — a security-relevant default that cannot be retrofitted to TS without breaking existing fleets. + +### Should you migrate now? + +Migrate **immediately** if any of the following applies: + +- You run more than ~5 instances simultaneously (fleet performance and daemon overhead). +- Your operators are on Windows or you want them to be. +- You depend on the TUI for fleet observability (web UI is enough on Rust too, but the TUI is a step up). +- You hit the cost-guard pause flow regularly — Rust honours the per-target gate at every outbound surface, plus the explicit-allowlist context for [`outbound_capabilities`](#fleet-yaml-schema-diff). + +You can **defer** if: + +- You run a single Telegram-bound instance, with one or two operators, no Discord, no Windows users. +- Your fleet config is stable and you do not need the new fields. +- You have not hit the JS `Number` precision issues yet (small fleet, no Discord, no Windows precision-sensitive usage). + +### Pre-alpha caveat + +`agend-terminal` is currently **pre-alpha**. The schema and CLI surface are still in flux — Sprint 22 P0 (`outbound_capabilities` flipping from optional to required) is one example; the Sprint 23 hard-error behaviour for the same field is another. Before migrating: + +1. **Pin the version.** Use a specific Cargo install / GitHub release tag, not `main`. +2. **Read each release's notes** before upgrading. The 2-stage transitions (warn-but-permit → hard error) move quickly between releases right now. +3. **Keep your `@suzuke/agend` install and `fleet.yaml` backup.** See [§6 Migration steps](#migration-steps) for the rollback procedure. + +The maintenance-mode commitment for `@suzuke/agend` is security fixes and backend CLI compatibility updates only — it will not gain new features. The migration window is open as long as `agend-terminal` is pre-1.0 and `@suzuke/agend` continues to receive security fixes. ## CLI flag mapping {#cli-flag-mapping} @@ -498,8 +539,157 @@ Rust does not currently expose tool-set profiles in `src/mcp/tools.rs` — every ## Migration steps {#migration-steps} -*(Phase C — covers: pre-flight checklist, `agend-terminal migrate` invocation if available, dual-run period, rollback, post-migration validation.)* +A 7-stage actionable plan. Sections 2 / 3 / 4 / 5 are the reference material; this section is the order of operations. + +### 1. Pre-migration audit (in TS) + +Inventory the state you depend on. Before touching `agend-terminal`, on the existing `@suzuke/agend` install: + +- `agend ls` — list every running instance plus its `working_directory` and `topic_id` if any. Save the output. +- `agend topic list` — list every Telegram topic binding. Save the output. +- `agend access list` — list every entry in `channel.access.allowed_users`. Save the output. Note whether your `access.mode` is `pairing` or `locked` — this matters for [Phase A High-friction #1](#fleet-yaml-schema-diff). +- `agend schedule list` — list every active schedule. Save the output. Note: schedules don't have a Rust CLI equivalent; you'll re-create them via the MCP tool surface or the TUI overlay (see [§2 Schedule group](#cli-flag-mapping)). +- Read `~/.agend/fleet.yaml` end-to-end. Know which fields you've populated; the [§3 fleet.yaml schema diff](#fleet-yaml-schema-diff) will tell you which of them have a Rust home and which don't. +- `agend fleet history` and `agend fleet activity` if you depend on either — note that neither has a Rust CLI equivalent (read log files directly under `$AGEND_HOME` on Rust). + +If you commit `AGENTS.md` / `GEMINI.md` to any repo your fleet touches, audit them now. Phase B documents the [OpenCode behaviour change to write `AGENTS.md`](#backend-invocation-diff); your `.gitignore` posture matters. + +### 2. Snapshot data + +Take three durable copies before you change anything: + +```bash +# Copy fleet config +cp ~/.agend/fleet.yaml ~/.agend/fleet.yaml.pre-migration.backup + +# Copy the entire $AGEND_HOME (decisions DB, instance state, daemon log, etc.) +tar czf ~/agend-home-pre-migration-$(date +%Y%m%d).tar.gz -C "$HOME" .agend + +# Note your @suzuke/agend version +agend --version > ~/agend-version-pre-migration.txt +``` + +The third file is the rollback target — write it down so you can re-install the exact same version if needed. + +### 3. Choose a migration mode + +- **Greenfield (fresh install)** — recommended unless you have meaningful historic state worth carrying. `agend-terminal init` from scratch with a hand-authored `fleet.yaml` derived from §3. Discard `@suzuke/agend` history except the backups in step 2. Faster, cleaner, surfaces schema differences early. +- **In-place** — copy `~/.agend/fleet.yaml` to the Rust daemon's config location, then mutate it through §3's diff table until the daemon accepts the load. Slower, more debugging, more useful for fleets with non-trivial decision history or template usage. **Do not skip step 2 if you go this route.** + +If you're undecided, start greenfield in a sandbox VM, see how it goes, and only do in-place once you're comfortable with the Rust schema and CLI shape. + +### 4. Workflow change: git worktree per branch + +`agend-terminal` expects each instance to spawn in a **unique working directory** — for git repos this means one git worktree per branch the daemon is operating on. This is encoded in the Rust resume strategy (each backend's `ContinueInCwd { flag }` mode keys "most recent session" by cwd, so distinct cwds are how distinct sessions stay distinct). It is also a hard rule in the global `CLAUDE.md` operator policy. + +If your `@suzuke/agend` workflow had multiple instances editing the same checkout (different branches via `git checkout`), you must change two things on Rust: + +```bash +# For each branch you want an instance to operate on: +git worktree add ../my-repo.worktrees/ + +# Then point the instance's working_directory at the worktree path: +# instances: +# worker-a: +# working_directory: /path/to/my-repo.worktrees/feat-x +``` + +This avoids checkout races between concurrent agents and lines up with the Rust daemon's session-per-cwd model. + +### 5. Cross-link integrate (apply the section diffs) + +This is the place where §2 / §3 / §4 / §5 actually get used. For each section, apply the mapping to your migrated `fleet.yaml`, prompts, and runbooks: + +- **CLI flag substitution** → re-read [§2 CLI flag mapping](#cli-flag-mapping). For every `agend …` invocation in your scripts, cron, or CI, replace it with the `agend-terminal` equivalent. If a row says **Removed**, follow the operator migration action listed in that row (read log files / edit `fleet.yaml` / use MCP tools / rely on OS-native services). +- **`fleet.yaml` field-by-field** → re-read [§3 fleet.yaml schema diff](#fleet-yaml-schema-diff). High-friction items #1 (`user_allowlist` fail-closed), #2 (`group_id` strict `i64`, un-quote on port), and #3 (`outbound_capabilities` Rust-only required field) are blocking — the daemon will refuse to load until they're addressed. The 14 removed `InstanceConfig` fields each have a documented Rust alternative (env var / backend preset / per-backend instruction file); apply them where relevant. +- **Backend invocation patterns** → re-read [§4 Backend invocation diff](#backend-invocation-diff). If you script CLI invocations directly (not just via the daemon), only Codex changed shape (resume is now the subcommand). Verify the three behaviour-change flags: OpenCode now writes `AGENTS.md`, Kiro switched to per-workdir naming, Kiro instructions are typed in as the first user message on Ready. +- **MCP tool API** → re-read [§5 MCP tool API diff](#mcp-tool-api-diff). The five prompt changes are listed in §5 ("Stop assuming push delivery is enough" through "Migrate `request_kind: 'report'` flows to use `reviewed_head`"). Update agent prompts and runbooks. + +### 6. Post-migration smoke test checklist + +Run these before declaring the migration done. Each tests one independent surface: + +- [ ] **Daemon starts.** `agend-terminal start --detached` (or your preferred launch path). `agend-terminal status` reports the daemon as alive and lists every configured instance. +- [ ] **Bot answers basic message.** From an allowlisted Telegram account, send any message. The bound instance receives it (`agend-terminal logs ` or pane scrollback shows the inbound), and replies — meaning `outbound_capabilities` is correctly configured (especially `reply`). +- [ ] **Inbound user-allowlist gate.** From a non-allowlisted Telegram account, send a message. Confirm the daemon log shows the inbound being rejected with the user_id stamped (`grep "outbound notify dropped" $AGEND_HOME/daemon.log` — remember `RUST_LOG=debug` if you don't see it). Bot does not reply. +- [ ] **Cross-instance dispatch.** Send `delegate_task` from one agent to another. The recipient sees the `[AGEND-MSG]` system reminder and can drain via `inbox`. `describe_message(message_id=…)` confirms the recipient picked up. +- [ ] **`set_waiting_on` round-trip.** Have one agent declare `set_waiting_on(condition=…)` and have a second agent `describe_instance()` to verify the field surfaces. +- [ ] **Cost-guard pre-check.** If you run with a `cost_guard` configured, deliberately push the target over its daily limit (or stub the `isLimited` value in a test) and confirm the sender gets the cost-guard error string, not silent drop. +- [ ] **CI watch loop** (only if you used `gh pr checks --watch` style polling). Issue `watch_ci(repo, branch)` and confirm a CI completion injects an inbox event without the agent polling. +- [ ] **Config reload.** Edit `fleet.yaml`, `agend-terminal stop`, `agend-terminal start`, confirm changes are picked up (Rust does not have a hot `reload` command — see [§2](#cli-flag-mapping)). + +If any item fails, do not proceed to step 7. Roll back if the failure blocks operators; debug and re-run otherwise. + +### 7. Rollback path + +If `agend-terminal` is taking on water and you need to back out: + +1. **Stop `agend-terminal`** (`agend-terminal stop`). +2. **Restore the pre-migration `$AGEND_HOME`** from the `.tar.gz` backup created in step 2. +3. **Re-install the previously-running `@suzuke/agend` version** (use the version string saved in step 2). +4. `agend start` and verify with the pre-migration smoke tests you did in step 1. +5. **File a `agend-terminal` issue** with the failure mode, including: + - The relevant lines from `daemon.log` (with `RUST_LOG=debug` if a fail-closed gate was hit). + - Your `fleet.yaml` with secrets redacted. + - The Rust binary version (`agend-terminal --version`). + +Keep the pre-migration backup for **at least 30 days** after a successful migration. Schema flux during pre-alpha may surface issues days after the cutover. ## Known incompatibilities and deferred parity {#known-incompatibilities} -*(Phase C — covers: features intentionally not ported, parity items deferred to a later Rust release, items still in design.)* +This is the explicit risk register. Every item here is something an operator should know **before** committing to migration, not discover after. + +### TS-only commands removed in `agend-terminal` + +Per [§2 CLI flag mapping](#cli-flag-mapping), these `agend …` invocations have no Rust CLI equivalent. Each row in §2 documents the operator workaround; the recap here is for risk surfacing: + +- `agend init` — replaced by `agend-terminal quickstart` (different prompt set). +- `agend restart` / `fleet restart` — use `stop` + `start`. +- `agend reload` — no hot reload; restart the daemon. +- `agend logs ` / `fleet logs` — read log files directly under `$AGEND_HOME`. +- `agend fleet history` / `fleet activity` / `fleet cleanup` — no equivalent. Stale instance dirs are not auto-cleaned; remove manually. +- `agend backend trust ` — manage backend trust by running the backend CLI once. +- `agend topic *` (list/bind/unbind) — declarative-only via `instances..topic_id` in `fleet.yaml`. +- `agend access *` (lock/unlock/list/remove/pair) — declarative via `channel.user_allowlist`. `pairing` mode has no equivalent (see below). +- `agend schedule *` — moved to MCP tools (`create_schedule` / `list_schedules` / etc.) and the TUI overlay. +- `agend update` — partial via `agend-terminal upgrade` (Unix only). +- `agend install` / `uninstall` — service registration delegated to `systemd` / `launchd`. +- `agend web` — replaced by the TUI (`agend-terminal app`). +- `agend export` / `import` / `export-chat` — no archive format. Copy `$AGEND_HOME` directly if you need state. +- `agend health` — folded into `agend-terminal doctor`. + +### TS-only fleet.yaml fields removed + +Per [§3 fleet.yaml schema diff](#fleet-yaml-schema-diff), 14 `InstanceConfig` fields plus 4 top-level keys have no Rust home. Each has a documented Rust alternative: + +- **Per-instance**: `tags`, `general_topic`, `restart_policy`, `context_guardian`, `log_level`, `tool_set`, `lightweight`, `systemPrompt`, `skipPermissions`, `model_failover`, `cost_guard`, `workflow`, `startup_timeout_ms`, `agent_mode`. +- **Top-level**: `project_roots`, `profiles`, `health_port`, `stt`. + +Drop the field; apply the Rust alternative if you depended on it (env var / backend preset / per-backend instruction file). The §3 table is the authoritative per-row guidance. + +### TS-only access semantics with no Rust analogue + +- **Pairing mode.** TS `AccessConfig.mode: "pairing"` (codes issued via `agend access pair`, redeemed by users to land their IDs in `channel.access.allowed_users`) has no Rust equivalent. On Rust, every authorised user must be enumerated explicitly in `channel.user_allowlist`. If your TS install used pairing mode, run `agend access list` before migrating and copy the resulting IDs into the Rust allowlist (also documented in [Phase A High-friction #1](#fleet-yaml-schema-diff)). + +### Pending parity (Rust roadmap) + +These are not removals — they are items where Rust currently lacks a TS feature, with a known plan to add it (or to harden the current behaviour). Do not depend on the *current* state past the milestone listed: + +- **`outbound_capabilities` 2-stage transition.** Sprint 22 P0 grants a "warn-but-permit one daemon cycle" grace; **Sprint 23 promotes the absent state to a hard parse error**. After Sprint 23 ships, every operator-defined instance must declare `outbound_capabilities` explicitly in `fleet.yaml` or the daemon will refuse to load it. Built-in coordinators (`general` and any future auto-created coordinator) auto-inject `[reply, react, edit, inject_provenance]`; user-authored entries do not. Detail in [Phase A High-friction #3](#fleet-yaml-schema-diff). +- **PTY transport / signal capability matrix.** [§4 Signal and ESC byte semantics](#backend-invocation-diff) marks four backends with `pending` for `interrupt` / `tool_kill` semantics. Sprint 11 of the Rust project will run real-CLI verification; until then, treat semantic claims about `interrupt` and `tool_kill` against the pending cells as unverified. +- **`AGEND_TOOL_SET` profiles.** TS exposes `standard` (12-tool) and `minimal` (4-tool) tool profiles via `AGEND_TOOL_SET` (`src/channel/mcp-tools.ts:120-126`). Rust currently exposes the full set (~45 tools) to every spawned agent. If you used `minimal` to reduce per-instance token overhead, expect higher token usage on Rust until a profile mechanism lands. Tracked as a follow-up; no committed Rust release. +- **Cross-channel architecture.** The `channel.user_allowlist` and `outbound_capabilities` gates are Telegram-first. Discord/Slack adapters will inherit the same gates via `auth.rs::gate_outbound_for_agent` once those channels reach feature parity; until then, expect channel-specific behaviour gaps. The `agend-terminal` `docs/MIGRATION-OUTBOUND-CAPS.md` operator deeper-dive has the cross-channel architecture note. +- **`list_instances` `tags` filter** (TS-only). [Phase B §5.2](#mcp-tool-api-diff) flags this as an open question — TS `list_instances` accepts a `tags` filter; current Rust takes no parameters. If you depended on tag-filtered instance listing, plan to enumerate manually until parity lands. + +### Functional limitations during transition + +- **`cost_guard`.** TS supports per-instance cost guard (overrides fleet defaults) plus the post-#57 outbound dispatch pre-check. Rust does not have a `cost_guard` field on `InstanceConfig` today. If you relied on per-instance cost limits, plan for a fleet-wide single-policy world on Rust until parity lands. +- **`channel.user_allowlist` requires explicit enumeration.** Pairing-mode users on TS must be enumerated to the Rust `channel.user_allowlist`. There is no flow on Rust that lets users "redeem" a code into the allowlist; operator action is required. +- **Discord guild ID must be un-quoted.** TS canonical was bare int for Telegram supergroup IDs and quoted strings for Discord guild IDs (to dodge JS `Number` precision loss). Rust accepts only bare int form for both. Un-quote any quoted `group_id` in the migrated `fleet.yaml`. Detail in [Phase A High-friction #2](#fleet-yaml-schema-diff). + +### Operator caveats + +- **`user_allowlist` drop log is at `DEBUG`, not `WARN`.** With default `RUST_LOG=info`, the `outbound notify dropped — channel not authorised` line is invisible — `grep` returns nothing and the natural conclusion ("config OK") is the wrong one. Set `RUST_LOG=debug` (or `RUST_LOG=agend_terminal=debug`) before reproducing access-gate failures. The `agend-terminal` Sprint 22 P1 backlog has an item to raise this to `WARN` for security-gate visibility; until then, the operator-facing caveat applies. +- **Pre-alpha schema instability.** As emphasised in [§1 Pre-alpha caveat](#why-migrate), pin the version, read each release's notes before upgrading, and keep your pre-migration backup for at least 30 days after the cutover. The 2-stage transitions (warn-but-permit → hard error) move quickly between releases right now. +- **No `agend-terminal` archive format yet.** Migrating fleet config across machines is a `tar` of `$AGEND_HOME` (or hand-copying `fleet.yaml`), not an `agend export` / `import` round trip. +- **`tmux` is not used on Rust.** If your operator habits include `tmux attach -t agend` or similar shortcuts, they will not work post-migration. Use `agend-terminal attach ` for direct PTY access and `agend-terminal app` for the multi-pane TUI. diff --git a/docs/migration-to-agend-terminal.zh-TW.md b/docs/migration-to-agend-terminal.zh-TW.md index 045536ae..938ff877 100644 --- a/docs/migration-to-agend-terminal.zh-TW.md +++ b/docs/migration-to-agend-terminal.zh-TW.md @@ -14,7 +14,48 @@ ## 為什麼要遷移? {#why-migrate} -*(Phase C —— 涵蓋:遷移動機、agend-terminal 相對 @suzuke/agend 的功能差異、何時不要遷移、支援的遷移期間。)* +`@suzuke/agend` 已進入 maintenance mode,新功能都在 `agend-terminal` 落地。本節是價值與成本的誠實版本 —— 直接,不行銷話術。 + +### 你會得到什麼 + +- **原生 PTY multiplexing。** Rust daemon 直接和 PTY 對話 (Unix 用 `openpty`,Windows 用 `ConPTY`)。`@suzuke/agend` 每次啟 backend 都 shell out 給 `tmux new-window`,把 tmux 的所有問題都繼承下來 (server 崩、stale window IDs、attach 怪行為)。Rust 上,daemon 自己的 TUI 就是 multiplexer。 +- **跨平台支援。** Rust 跑 macOS / Linux / Windows;TS 只有 macOS / Linux (`tmux` 在 Windows 跑不起來)。`which::which` 會看 `PATHEXT`,所以 Windows 上的 `claude.cmd` / `codex.ps1` 可正確解析。 +- **Type safety 落在重要的地方。** [§3 fleet.yaml schema diff](#fleet-yaml-schema-diff) 中的遷移項目 —— `group_id` 精度、`topic_id` 寬度、`outbound_capabilities` enum 收斂 —— Rust 在 config 載入時就檢查。TS daemon 是透過 bug report 學到同一組教訓。 +- **Async daemon,沒有 per-instance Node process。** Rust 把每個 agent 以子程序形式起在單一 daemon binary 之下。TS daemon 是單一 Node process,但每實例的 runtime overhead 比 Rust task 高很多;重 fleet (>5 instance 同時跑) 感受最深。 +- **單一真相來源的 backend 表。** `BackendPreset` 集中每個 backend 的 spawn flags / resume mode / instructions delivery。TS 上同一個面向被切到五個 `CliBackend` class,per-backend 行為會漂移。詳見 [§4 Backend invocation diff](#backend-invocation-diff)。 +- **內建 TUI。** `agend-terminal app` 在運行中的 daemon 之上提供多 pane 終端機介面。`@suzuke/agend` 只有 web UI (`agend web`);TUI 是 Rust 獨有功能。 + +### 為什麼 `@suzuke/agend` 進入 deprecation + +- **JS `Number.MAX_SAFE_INTEGER`** (2^53 − 1) 在你用 Discord guild ID 但沒 quote 時就咬人;Telegram supergroup ID 安然在 threshold 之下,但「永遠 quote 大型 ID」的紀律在 codebase 各處執行得不一致。Rust 用 `i64` 兩種都用裸 int 形式涵蓋 —— 詳見 [High-friction #2](#fleet-yaml-schema-diff)。 +- **`tmux` 作為 multiplexer** 把 daemon 完全鎖出 Windows,並且多了一層 bug 容易藏的地方 (「pane 黑掉了;是 agent、tmux server、還是 wrapper script 出問題?」)。 +- **Process management overhead。** TS 上每個 spawn 出來的 backend 住在 tmux pane 裡,daemon 用「signal-capturing wrapper script + PTY-output regex」管理。Rust 直接驅動 PTY。 +- **Implicit channel ACLs。** TS 把所有 outbound MCP 呼叫視為「任何 instance 都能用」。Rust PR #230 引入明確的 `outbound_capabilities` allowlist —— 一個 security-relevant 的預設,沒辦法在不破壞既有 fleet 的前提下回填到 TS。 + +### 應該現在遷移嗎? + +**立刻遷移**,如果你符合任何下列情況: + +- 你同時跑超過 ~5 個 instance (fleet 效能與 daemon overhead)。 +- 你的 operator 在 Windows,或你希望他們可以在 Windows。 +- 你依賴 TUI 做 fleet 觀測 (Rust 的 web UI 也 OK,但 TUI 是更上一階)。 +- 你常踩到 cost-guard pause 流程 —— Rust 在每個 outbound 介面都 honor per-target gate,並有 [`outbound_capabilities`](#fleet-yaml-schema-diff) 的明確 allowlist 配套。 + +**可以延後**,如果: + +- 你只跑一個 Telegram-bound instance、一兩個 operator、沒 Discord、沒 Windows 使用者。 +- 你的 fleet config 穩定,不需要新欄位。 +- 你還沒踩過 JS `Number` 精度問題 (小 fleet、無 Discord、無 Windows 精度敏感場景)。 + +### Pre-alpha 注意事項 + +`agend-terminal` 目前是 **pre-alpha**。Schema 與 CLI 介面仍在變動 —— Sprint 22 P0 (`outbound_capabilities` 從 optional 翻成 required) 是一個例子;Sprint 23 同欄位將 absent 升為 hard parse error 是另一個。遷移前: + +1. **鎖版本。** 用特定 Cargo install / GitHub release tag,不是 `main`。 +2. **每次升級前讀 release notes。** 兩階段轉變 (warn-but-permit → hard error) 在這時期 release 之間移動很快。 +3. **保留 `@suzuke/agend` 安裝與 `fleet.yaml` 備份。** Rollback 流程見 [§6 Migration steps](#migration-steps)。 + +`@suzuke/agend` 的 maintenance-mode 承諾僅限 security 修補與 backend CLI 相容性更新 —— 不再加新功能。遷移窗口在 `agend-terminal` 仍 pre-1.0、且 `@suzuke/agend` 持續收 security fix 之間都開著。 ## CLI flag mapping {#cli-flag-mapping} @@ -496,8 +537,157 @@ Rust 目前在 `src/mcp/tools.rs` 沒有暴露 tool-set profile —— 每個 sp ## Migration steps {#migration-steps} -*(Phase C —— 涵蓋:pre-flight 檢查清單、`agend-terminal migrate` (若提供) 呼叫、雙跑期間、rollback、post-migration 驗證。)* +7 階段 actionable plan。第 2 / 3 / 4 / 5 節是參考材料,本節是執行順序。 + +### 1. 遷移前 audit (在 TS 上) + +清點你依賴的狀態。動 `agend-terminal` 之前,在既有 `@suzuke/agend` 安裝上執行: + +- `agend ls` —— 列出每個運行中的 instance、其 `working_directory` 與 `topic_id`。存下輸出。 +- `agend topic list` —— 列出每個 Telegram topic binding。存下輸出。 +- `agend access list` —— 列出 `channel.access.allowed_users` 的每個條目。存下輸出。記下你的 `access.mode` 是 `pairing` 還是 `locked` —— 這對 [Phase A High-friction #1](#fleet-yaml-schema-diff) 很重要。 +- `agend schedule list` —— 列出每個 active 排程。存下輸出。注意:Rust 沒有 schedule CLI 對等,要透過 MCP tool 或 TUI overlay 重建 (見 [§2 Schedule group](#cli-flag-mapping))。 +- 從頭到尾讀過 `~/.agend/fleet.yaml`。確認你填了哪些欄位;[§3 fleet.yaml schema diff](#fleet-yaml-schema-diff) 會告訴你哪些有 Rust 對等、哪些沒有。 +- `agend fleet history` 與 `agend fleet activity` (若有依賴) —— 注意兩者 Rust 端都沒 CLI 對等 (Rust 上直接讀 `$AGEND_HOME` 下的 log 檔)。 + +如果 fleet 操作的 repo 有 commit `AGENTS.md` / `GEMINI.md`,現在就 audit。Phase B 已記載 [OpenCode 改寫 `AGENTS.md` 的行為變化](#backend-invocation-diff);你的 `.gitignore` 姿態會有影響。 + +### 2. Snapshot 資料 + +動任何東西之前,做三份耐久備份: + +```bash +# 複製 fleet config +cp ~/.agend/fleet.yaml ~/.agend/fleet.yaml.pre-migration.backup + +# 整個 $AGEND_HOME 打包 (decisions DB、instance state、daemon log 等) +tar czf ~/agend-home-pre-migration-$(date +%Y%m%d).tar.gz -C "$HOME" .agend + +# 記下 @suzuke/agend 版本 +agend --version > ~/agend-version-pre-migration.txt +``` + +第三個檔案是 rollback 目標 —— 寫下來,需要時可以裝同一個版本。 + +### 3. 選遷移模式 + +- **Greenfield (全新安裝)** —— 推薦,除非你有值得帶過去的歷史狀態。`agend-terminal init` 從頭跑,手寫一份依 §3 推導的 `fleet.yaml`。除了步驟 2 的備份,捨棄 `@suzuke/agend` 歷史。較快、較乾淨、schema 差異會早點浮現。 +- **In-place** —— 把 `~/.agend/fleet.yaml` 複製到 Rust daemon 的 config 位置,然後依 §3 的 diff 表逐項調整,直到 daemon 願意載入。較慢、較多 debug,適合有非平凡 decision 歷史或 template 用法的 fleet。**這條路千萬不要省掉步驟 2**。 + +如果不確定怎麼選,先在 sandbox VM 跑一次 greenfield、看順不順,再回來做 in-place。 + +### 4. Workflow 改變:每 branch 一個 git worktree + +`agend-terminal` 期待每個 instance 跑在 **獨立的 working directory** —— 對 git repo 來說,代表 daemon 操作的每個 branch 都要一個 worktree。這是 Rust resume 策略隱含的 (各 backend 的 `ContinueInCwd { flag }` 模式以 cwd 為 key 找「最近 session」,所以不同 cwd 才會對應到不同 session)。也是全域 `CLAUDE.md` operator policy 的硬規則。 + +如果你的 `@suzuke/agend` workflow 是多個 instance 在同一個 checkout 上 (用 `git checkout` 切 branch) 編輯,Rust 上你需要改兩件事: + +```bash +# 對你想讓 instance 操作的每個 branch: +git worktree add ../my-repo.worktrees/ + +# 然後讓 instance 的 working_directory 指到 worktree 路徑: +# instances: +# worker-a: +# working_directory: /path/to/my-repo.worktrees/feat-x +``` + +這避免並行 agent 之間的 checkout race,並且和 Rust daemon 的 session-per-cwd 模型對齊。 + +### 5. Cross-link integrate (套用各節 diff) + +這是真正動用 §2 / §3 / §4 / §5 的時刻。逐節把 mapping 套用到你 migrate 後的 `fleet.yaml`、prompts、runbook: + +- **CLI flag substitution** → 重讀 [§2 CLI flag mapping](#cli-flag-mapping)。每個你 script、cron、CI 中的 `agend …` 呼叫,換成 `agend-terminal` 的對等。如果該 row 寫 **Removed**,就走那 row 列出的 operator migration action (讀 log 檔 / 改 `fleet.yaml` / 用 MCP tool / 依賴 OS-native service)。 +- **`fleet.yaml` 逐欄位** → 重讀 [§3 fleet.yaml schema diff](#fleet-yaml-schema-diff)。三個 high-friction 項 #1 (`user_allowlist` fail-closed)、#2 (`group_id` 嚴格 `i64`,port 時 un-quote)、#3 (`outbound_capabilities` Rust-only required) 都是阻擋級的 —— 沒處理 daemon 不會載入。14 個被移除的 `InstanceConfig` 欄位都有 Rust 替代位置 (env var / backend preset / per-backend instruction file),按需要套用。 +- **Backend invocation 模式** → 重讀 [§4 Backend invocation diff](#backend-invocation-diff)。如果你直接 script CLI invocation (不是只透過 daemon),只有 Codex 形狀變了 (resume 回到子命令)。確認三個 behavior change flag:OpenCode 改寫 `AGENTS.md`、Kiro 改 per-workdir 命名、Kiro 改在 Ready 後當 user message 注入。 +- **MCP tool API** → 重讀 [§5 MCP tool API diff](#mcp-tool-api-diff)。§5 列出五項 prompt 改動 (從「不要再假設 push delivery 夠」一直到「`request_kind: 'report'` 改用 `reviewed_head`」)。更新 agent prompts 與 runbook。 + +### 6. 遷移後 smoke test 清單 + +在宣告遷移完成前跑這些。每項測一個獨立面向: + +- [ ] **Daemon 啟動。** `agend-terminal start --detached` (或你慣用的啟動路徑)。`agend-terminal status` 回報 daemon alive 並列出每個 configured instance。 +- [ ] **Bot 回基本訊息。** 從 allowlisted 的 Telegram 帳號發任何訊息。綁定 instance 收到 (`agend-terminal logs ` 或 pane scrollback 顯示 inbound) 並回覆 —— 代表 `outbound_capabilities` (尤其 `reply`) 設定正確。 +- [ ] **Inbound user-allowlist gate。** 從非 allowlisted 帳號發訊息。確認 daemon log 有對應 user_id 的 inbound 拒絕記錄 (`grep "outbound notify dropped" $AGEND_HOME/daemon.log` —— 沒看到記得 `RUST_LOG=debug`)。Bot 不回覆。 +- [ ] **跨實例 dispatch。** 從一個 agent 對另一個發 `delegate_task`。收件方看到 `[AGEND-MSG]` system reminder,可透過 `inbox` drain。`describe_message(message_id=…)` 確認 pickup。 +- [ ] **`set_waiting_on` round-trip。** 一個 agent 宣告 `set_waiting_on(condition=…)`,第二個 agent `describe_instance()` 確認欄位浮現。 +- [ ] **Cost-guard 預檢。** 若有設 `cost_guard`,刻意把 target 推過日預算 (或在測試中 stub `isLimited`),確認 sender 拿到 cost-guard error 字串而非靜默 drop。 +- [ ] **CI watch loop** (僅在你之前用 `gh pr checks --watch` 輪詢時相關)。發 `watch_ci(repo, branch)`,確認 CI 結束時自動注 inbox event,不需 agent 自己輪詢。 +- [ ] **Config 重載。** 改 `fleet.yaml`、`agend-terminal stop`、`agend-terminal start`,確認改動生效 (Rust 沒有熱 `reload` —— 見 [§2](#cli-flag-mapping))。 + +任何一項失敗,**不要** 進到步驟 7。失敗阻擋 operator 就 rollback;否則就 debug 後重跑。 + +### 7. Rollback 路徑 + +如果 `agend-terminal` 進水你需要退回: + +1. **停 `agend-terminal`** (`agend-terminal stop`)。 +2. **從步驟 2 的 `.tar.gz` 還原 pre-migration 的 `$AGEND_HOME`**。 +3. **重新安裝先前運行的 `@suzuke/agend` 版本** (用步驟 2 存的版本字串)。 +4. `agend start`,並用步驟 1 做的遷移前 smoke test 驗證。 +5. **送一個 `agend-terminal` issue**,內容包含: + - `daemon.log` 裡相關行 (若踩到 fail-closed gate,記得帶 `RUST_LOG=debug` 重現再貼)。 + - 你的 `fleet.yaml`,secrets 已 redact。 + - Rust binary 版本 (`agend-terminal --version`)。 + +成功遷移後請 **保留 pre-migration 備份至少 30 天**。Pre-alpha 期間 schema 變動,可能在 cutover 後幾天才浮現問題。 ## Known incompatibilities and deferred parity {#known-incompatibilities} -*(Phase C —— 涵蓋:意圖不 port 的功能、被延後到後續 Rust release 的 parity 項目、仍在設計中的項目。)* +這節是明確的 risk register。每一項都是 operator 應該在 **承諾遷移前** 就知道、而不是事後才發現。 + +### `agend-terminal` 移除的 TS-only 指令 + +依 [§2 CLI flag mapping](#cli-flag-mapping),下列 `agend …` 呼叫 Rust 沒有 CLI 對等。每一項在 §2 都記載 operator 解法;這裡 recap 為了把 risk 攤開: + +- `agend init` —— 由 `agend-terminal quickstart` 取代 (問題集合不同)。 +- `agend restart` / `fleet restart` —— 用 `stop` + `start`。 +- `agend reload` —— 沒有熱重載;改 daemon 需要 stop/start。 +- `agend logs ` / `fleet logs` —— 直接讀 `$AGEND_HOME` 下的 log 檔。 +- `agend fleet history` / `fleet activity` / `fleet cleanup` —— 無對等。Stale instance 資料夾不自動清,需要時手動刪。 +- `agend backend trust ` —— 改成手動跑一次 backend CLI 接受其 trust 對話框。 +- `agend topic *` (list/bind/unbind) —— 改宣告式,只透過 `instances..topic_id` 在 `fleet.yaml` 設定。 +- `agend access *` (lock/unlock/list/remove/pair) —— 改宣告式,透過 `channel.user_allowlist`。`pairing` 模式無對等 (見下)。 +- `agend schedule *` —— 搬到 MCP tool (`create_schedule` / `list_schedules` 等) 與 TUI overlay。 +- `agend update` —— 部分由 `agend-terminal upgrade` 涵蓋 (僅 Unix)。 +- `agend install` / `uninstall` —— Service 註冊改交 `systemd` / `launchd`。 +- `agend web` —— 由 TUI (`agend-terminal app`) 取代。 +- `agend export` / `import` / `export-chat` —— 沒有 archive 格式。需要備份就直接複製 `$AGEND_HOME`。 +- `agend health` —— 折進 `agend-terminal doctor`。 + +### 移除的 TS-only fleet.yaml 欄位 + +依 [§3 fleet.yaml schema diff](#fleet-yaml-schema-diff),14 個 `InstanceConfig` 欄位 + 4 個 top-level 鍵 Rust 沒有對應位置。每個都有 Rust 替代方案: + +- **Per-instance**:`tags`、`general_topic`、`restart_policy`、`context_guardian`、`log_level`、`tool_set`、`lightweight`、`systemPrompt`、`skipPermissions`、`model_failover`、`cost_guard`、`workflow`、`startup_timeout_ms`、`agent_mode`。 +- **Top-level**:`project_roots`、`profiles`、`health_port`、`stt`。 + +砍掉欄位;有依賴的話套用 Rust 替代 (env var / backend preset / per-backend instruction file)。§3 的表是逐 row 的權威指引。 + +### TS-only 存取語意 Rust 沒有對應 + +- **Pairing mode。** TS `AccessConfig.mode: "pairing"` (透過 `agend access pair` 發碼,使用者兌換後 ID 進到 `channel.access.allowed_users`) Rust 無對等。Rust 上每個授權使用者必須明確列在 `channel.user_allowlist`。如果你的 TS 安裝是 pairing mode,遷移前先 `agend access list`,把結果列進 Rust allowlist (也記載在 [Phase A High-friction #1](#fleet-yaml-schema-diff))。 + +### 待補的 parity (Rust roadmap) + +這些不是「移除」—— 是 Rust 目前缺 TS 功能、且有計畫補 (或硬化現有行為) 的項目。**不要** 依賴 *目前* 行為跨過下面所列的里程碑: + +- **`outbound_capabilities` 兩階段 transition。** Sprint 22 P0 給「warn-but-permit one daemon cycle」grace;**Sprint 23 把 absent 升為 hard parse error**。Sprint 23 ship 後,operator 自寫的每個 instance 必須在 `fleet.yaml` 明確宣告 `outbound_capabilities`,不然 daemon 拒絕載入。Built-in coordinator (`general` 與未來自動建立的 coordinator) 自動注入 `[reply, react, edit, inject_provenance]`;user-authored entry 不會自動注入。詳見 [Phase A High-friction #3](#fleet-yaml-schema-diff)。 +- **PTY transport / signal capability matrix。** [§4 信號與 ESC byte 語意](#backend-invocation-diff) 把四個 backend 的 `interrupt` / `tool_kill` 語意標 `pending`。Rust 專案 Sprint 11 會做 real-CLI 驗證;在那之前,對應 cell 上的 semantic 主張請當作未驗證。 +- **`AGEND_TOOL_SET` profile。** TS 透過 `AGEND_TOOL_SET` 開放 `standard` (12-tool) 與 `minimal` (4-tool) tool profile (`src/channel/mcp-tools.ts:120-126`)。Rust 目前對所有 spawn 出來的 agent 都暴露完整 set (約 45 個工具)。如果你之前用 `minimal` 降低 per-instance token overhead,Rust 上 token 用量會比較高,直到 profile 機制實作。Tracked as follow-up;沒有 committed 的 Rust release。 +- **跨 channel 架構。** `channel.user_allowlist` 與 `outbound_capabilities` 兩個 gate 目前以 Telegram 為主。Discord/Slack adapter 在 channel parity 完成後會透過 `auth.rs::gate_outbound_for_agent` 繼承同一組 gate;在那之前,各 channel 行為可能仍有落差。`agend-terminal` 的 `docs/MIGRATION-OUTBOUND-CAPS.md` operator 完整參考有跨 channel 架構備註。 +- **`list_instances` 的 `tags` filter** (TS-only)。[Phase B §5.2](#mcp-tool-api-diff) 標記為 open question —— TS `list_instances` 接 `tags` filter,Rust 目前不接參數。如果你依賴 tag-filter 的 instance 列舉,在 parity 補上前請改手動列。 + +### 過渡期的功能限制 + +- **`cost_guard`。** TS 支援 per-instance cost guard (覆寫 fleet 預設) 加上 #57 後的 outbound dispatch pre-check。Rust 目前 `InstanceConfig` 沒 `cost_guard` 欄位。如果你依賴 per-instance 成本上限,計畫好 Rust 上是 fleet-wide 單一政策的世界,直到 parity 補上。 +- **`channel.user_allowlist` 必須明確列舉。** TS 上的 pairing-mode 使用者必須列進 Rust 的 `channel.user_allowlist`。Rust 上沒有讓使用者「兌換 code 進 allowlist」的流程;需要 operator 動作。 +- **Discord guild ID 必須 un-quote。** TS canonical 對 Telegram supergroup ID 用裸 int、對 Discord guild ID 用 quoted string (避開 JS `Number` 精度)。Rust 兩種都只接受裸 int。Migrate 後的 `fleet.yaml` 中,任何 quoted `group_id` 都要 un-quote。詳見 [Phase A High-friction #2](#fleet-yaml-schema-diff)。 + +### Operator 注意事項 + +- **`user_allowlist` drop log 是 `DEBUG`,不是 `WARN`。** 在預設 `RUST_LOG=info` 下,`outbound notify dropped — channel not authorised` 那行看不到 —— `grep` 找不到,自然結論「config OK」是錯的。重現存取 gate 失敗時請先設 `RUST_LOG=debug` (或 `RUST_LOG=agend_terminal=debug`)。`agend-terminal` Sprint 22 P1 backlog 有把這行升為 `WARN` 的項目;在那之前 operator-facing caveat 仍然成立。 +- **Pre-alpha schema 不穩定。** 如 [§1 Pre-alpha 注意事項](#why-migrate) 強調,鎖版本、每次升級前讀 release notes、cutover 後保留 pre-migration 備份至少 30 天。兩階段 transition (warn-but-permit → hard error) 在這時期 release 之間移動很快。 +- **目前沒有 `agend-terminal` archive 格式。** 跨機器搬 fleet config 是 `tar` 整個 `$AGEND_HOME` (或手動 copy `fleet.yaml`),不是 `agend export` / `import` round trip。 +- **Rust 不用 `tmux`。** 如果你的 operator 習慣含 `tmux attach -t agend` 之類捷徑,遷移後不適用。改用 `agend-terminal attach ` 直接接 PTY,或 `agend-terminal app` 進多 pane TUI。 From 1fc19d4d900500aca4ff202d521b7ed444a14767 Mon Sep 17 00:00:00 2001 From: suzuke Date: Mon, 27 Apr 2026 13:15:17 +0800 Subject: [PATCH 2/3] docs(migration): address Phase C dual-review findings (M1+L1+O1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three findings from the parallel ts-reviewer + dev-reviewer-2 audit on PR #66 reviewed_head 9638d5e7. All three addressed in EN and zh-TW. M1 (medium, mandatory) — §7 Pending parity Sprint 11 timeline reframe: - dev-reviewer-2 F1: doc said "Sprint 11 of the Rust project will run real-CLI verification" as future-tense. agend-terminal is at Sprint 22 (post-#230 merge); Sprint 11 is the *filing* sprint of the backlog item, not a future schedule. Real-CLI verification has no committed sprint window in the Sprint 22-25 roadmap, and the operator has the work itself under review for cancellation. - Re-worded per reviewer's suggestion: cite the specific backlog id `t-20260425040356199333-6`, surface that Sprint 22-25 has not committed completion, and surface that the work is under operator review. L1 (low, took the polish) — §1.1 PTY primitive abstraction caveat: - dev-reviewer-2 F2: "Rust daemon talks to PTYs directly (`openpty` on Unix, `ConPTY` on Windows)" elides the `portable-pty` crate layer. Materially correct (no tmux, native primitives) but not technically precise. - Polished to "via the cross-platform `portable-pty` crate (which uses `openpty` on Unix, `ConPTY` on Windows)" — operator now sees the abstraction layer without losing the no-tmux/native-primitives takeaway. O1 (low, took the polish) — §6 step 6 cross-instance dispatch busy-gate note: - ts-reviewer O1: smoke test step uses `delegate_task` without noting that post-PR #197/#199 (Rust Sprint 21), `delegate_task` against a mid-LLM-turn receiver returns BUSY unless `force=true`. Smoke test is operationally fine when both agents are idle, but the operator who hits busy will ask why. - Added one-sentence guidance: ensure both agents idle, or pass `force: true` to exercise the busy-override path. ts-lead recommended fixing all three since they are surgical single-line text edits and doc-quality benefit > revision cost. New commit on top of 9638d5e7 per CLAUDE.md (no amend). Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/migration-to-agend-terminal.md | 6 +++--- docs/migration-to-agend-terminal.zh-TW.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/migration-to-agend-terminal.md b/docs/migration-to-agend-terminal.md index 67be9f08..37f59102 100644 --- a/docs/migration-to-agend-terminal.md +++ b/docs/migration-to-agend-terminal.md @@ -20,7 +20,7 @@ ### What you gain -- **Native PTY multiplexing.** The Rust daemon talks to PTYs directly (`openpty` on Unix, `ConPTY` on Windows). `@suzuke/agend` shells out to `tmux new-window` for every backend launch and inherits every tmux gotcha (server crashes, stale window IDs, attach quirks). On Rust, the daemon's own TUI is the multiplexer. +- **Native PTY multiplexing.** The Rust daemon talks to PTYs directly via the cross-platform `portable-pty` crate (which uses `openpty` on Unix, `ConPTY` on Windows). `@suzuke/agend` shells out to `tmux new-window` for every backend launch and inherits every tmux gotcha (server crashes, stale window IDs, attach quirks). On Rust, the daemon's own TUI is the multiplexer. - **Cross-platform support.** macOS / Linux / Windows on Rust; macOS / Linux only on TS (`tmux` does not run natively on Windows). `which::which` honours `PATHEXT` so `claude.cmd` / `codex.ps1` resolve correctly on Windows. - **Type safety where it matters.** The migration items in [§3 fleet.yaml schema diff](#fleet-yaml-schema-diff) — `group_id` precision, `topic_id` width, `outbound_capabilities` enum closure — are checked at config load on Rust. The TS daemon learned the same lessons through bug reports. - **Async daemon, no per-instance Node process.** Rust spawns each agent as a child process under one daemon binary. The TS daemon is a single Node process but the runtime overhead per instance is materially higher than a Rust task; heavy fleets (>5 simultaneous instances) feel this most. @@ -612,7 +612,7 @@ Run these before declaring the migration done. Each tests one independent surfac - [ ] **Daemon starts.** `agend-terminal start --detached` (or your preferred launch path). `agend-terminal status` reports the daemon as alive and lists every configured instance. - [ ] **Bot answers basic message.** From an allowlisted Telegram account, send any message. The bound instance receives it (`agend-terminal logs ` or pane scrollback shows the inbound), and replies — meaning `outbound_capabilities` is correctly configured (especially `reply`). - [ ] **Inbound user-allowlist gate.** From a non-allowlisted Telegram account, send a message. Confirm the daemon log shows the inbound being rejected with the user_id stamped (`grep "outbound notify dropped" $AGEND_HOME/daemon.log` — remember `RUST_LOG=debug` if you don't see it). Bot does not reply. -- [ ] **Cross-instance dispatch.** Send `delegate_task` from one agent to another. The recipient sees the `[AGEND-MSG]` system reminder and can drain via `inbox`. `describe_message(message_id=…)` confirms the recipient picked up. +- [ ] **Cross-instance dispatch.** Send `delegate_task` from one agent to another. The recipient sees the `[AGEND-MSG]` system reminder and can drain via `inbox`. `describe_message(message_id=…)` confirms the recipient picked up. Ensure both agents are idle when running this test, or pass `force: true` if you want to exercise the busy-override path — `delegate_task` against a mid-LLM-turn receiver returns BUSY by default (post-PR #197/#199 in Rust). - [ ] **`set_waiting_on` round-trip.** Have one agent declare `set_waiting_on(condition=…)` and have a second agent `describe_instance()` to verify the field surfaces. - [ ] **Cost-guard pre-check.** If you run with a `cost_guard` configured, deliberately push the target over its daily limit (or stub the `isLimited` value in a test) and confirm the sender gets the cost-guard error string, not silent drop. - [ ] **CI watch loop** (only if you used `gh pr checks --watch` style polling). Issue `watch_ci(repo, branch)` and confirm a CI completion injects an inbox event without the agent polling. @@ -676,7 +676,7 @@ Drop the field; apply the Rust alternative if you depended on it (env var / back These are not removals — they are items where Rust currently lacks a TS feature, with a known plan to add it (or to harden the current behaviour). Do not depend on the *current* state past the milestone listed: - **`outbound_capabilities` 2-stage transition.** Sprint 22 P0 grants a "warn-but-permit one daemon cycle" grace; **Sprint 23 promotes the absent state to a hard parse error**. After Sprint 23 ships, every operator-defined instance must declare `outbound_capabilities` explicitly in `fleet.yaml` or the daemon will refuse to load it. Built-in coordinators (`general` and any future auto-created coordinator) auto-inject `[reply, react, edit, inject_provenance]`; user-authored entries do not. Detail in [Phase A High-friction #3](#fleet-yaml-schema-diff). -- **PTY transport / signal capability matrix.** [§4 Signal and ESC byte semantics](#backend-invocation-diff) marks four backends with `pending` for `interrupt` / `tool_kill` semantics. Sprint 11 of the Rust project will run real-CLI verification; until then, treat semantic claims about `interrupt` and `tool_kill` against the pending cells as unverified. +- **PTY transport / signal capability matrix.** [§4 Signal and ESC byte semantics](#backend-invocation-diff) marks four backends with `pending` for `interrupt` / `tool_kill` semantics. Real-CLI verification is currently tracked as a backlog item filed in Sprint 11 (`t-20260425040356199333-6`); no committed sprint for completion in the Sprint 22-25 roadmap, and the operator has the work itself under "is this still worth doing?" review. Treat semantic claims about `interrupt` and `tool_kill` against the pending cells as unverified. - **`AGEND_TOOL_SET` profiles.** TS exposes `standard` (12-tool) and `minimal` (4-tool) tool profiles via `AGEND_TOOL_SET` (`src/channel/mcp-tools.ts:120-126`). Rust currently exposes the full set (~45 tools) to every spawned agent. If you used `minimal` to reduce per-instance token overhead, expect higher token usage on Rust until a profile mechanism lands. Tracked as a follow-up; no committed Rust release. - **Cross-channel architecture.** The `channel.user_allowlist` and `outbound_capabilities` gates are Telegram-first. Discord/Slack adapters will inherit the same gates via `auth.rs::gate_outbound_for_agent` once those channels reach feature parity; until then, expect channel-specific behaviour gaps. The `agend-terminal` `docs/MIGRATION-OUTBOUND-CAPS.md` operator deeper-dive has the cross-channel architecture note. - **`list_instances` `tags` filter** (TS-only). [Phase B §5.2](#mcp-tool-api-diff) flags this as an open question — TS `list_instances` accepts a `tags` filter; current Rust takes no parameters. If you depended on tag-filtered instance listing, plan to enumerate manually until parity lands. diff --git a/docs/migration-to-agend-terminal.zh-TW.md b/docs/migration-to-agend-terminal.zh-TW.md index 938ff877..cbc003b8 100644 --- a/docs/migration-to-agend-terminal.zh-TW.md +++ b/docs/migration-to-agend-terminal.zh-TW.md @@ -18,7 +18,7 @@ ### 你會得到什麼 -- **原生 PTY multiplexing。** Rust daemon 直接和 PTY 對話 (Unix 用 `openpty`,Windows 用 `ConPTY`)。`@suzuke/agend` 每次啟 backend 都 shell out 給 `tmux new-window`,把 tmux 的所有問題都繼承下來 (server 崩、stale window IDs、attach 怪行為)。Rust 上,daemon 自己的 TUI 就是 multiplexer。 +- **原生 PTY multiplexing。** Rust daemon 透過跨平台的 `portable-pty` crate 直接和 PTY 對話 (該 crate 在 Unix 用 `openpty`,在 Windows 用 `ConPTY`)。`@suzuke/agend` 每次啟 backend 都 shell out 給 `tmux new-window`,把 tmux 的所有問題都繼承下來 (server 崩、stale window IDs、attach 怪行為)。Rust 上,daemon 自己的 TUI 就是 multiplexer。 - **跨平台支援。** Rust 跑 macOS / Linux / Windows;TS 只有 macOS / Linux (`tmux` 在 Windows 跑不起來)。`which::which` 會看 `PATHEXT`,所以 Windows 上的 `claude.cmd` / `codex.ps1` 可正確解析。 - **Type safety 落在重要的地方。** [§3 fleet.yaml schema diff](#fleet-yaml-schema-diff) 中的遷移項目 —— `group_id` 精度、`topic_id` 寬度、`outbound_capabilities` enum 收斂 —— Rust 在 config 載入時就檢查。TS daemon 是透過 bug report 學到同一組教訓。 - **Async daemon,沒有 per-instance Node process。** Rust 把每個 agent 以子程序形式起在單一 daemon binary 之下。TS daemon 是單一 Node process,但每實例的 runtime overhead 比 Rust task 高很多;重 fleet (>5 instance 同時跑) 感受最深。 @@ -610,7 +610,7 @@ git worktree add ../my-repo.worktrees/ - [ ] **Daemon 啟動。** `agend-terminal start --detached` (或你慣用的啟動路徑)。`agend-terminal status` 回報 daemon alive 並列出每個 configured instance。 - [ ] **Bot 回基本訊息。** 從 allowlisted 的 Telegram 帳號發任何訊息。綁定 instance 收到 (`agend-terminal logs ` 或 pane scrollback 顯示 inbound) 並回覆 —— 代表 `outbound_capabilities` (尤其 `reply`) 設定正確。 - [ ] **Inbound user-allowlist gate。** 從非 allowlisted 帳號發訊息。確認 daemon log 有對應 user_id 的 inbound 拒絕記錄 (`grep "outbound notify dropped" $AGEND_HOME/daemon.log` —— 沒看到記得 `RUST_LOG=debug`)。Bot 不回覆。 -- [ ] **跨實例 dispatch。** 從一個 agent 對另一個發 `delegate_task`。收件方看到 `[AGEND-MSG]` system reminder,可透過 `inbox` drain。`describe_message(message_id=…)` 確認 pickup。 +- [ ] **跨實例 dispatch。** 從一個 agent 對另一個發 `delegate_task`。收件方看到 `[AGEND-MSG]` system reminder,可透過 `inbox` drain。`describe_message(message_id=…)` 確認 pickup。請確保兩個 agent 都 idle 時跑這個測試,或若想驗證 busy-override 路徑就傳 `force: true` —— 對 mid-LLM-turn 收件方發 `delegate_task` 預設會回 BUSY (Rust 端 post-PR #197/#199)。 - [ ] **`set_waiting_on` round-trip。** 一個 agent 宣告 `set_waiting_on(condition=…)`,第二個 agent `describe_instance()` 確認欄位浮現。 - [ ] **Cost-guard 預檢。** 若有設 `cost_guard`,刻意把 target 推過日預算 (或在測試中 stub `isLimited`),確認 sender 拿到 cost-guard error 字串而非靜默 drop。 - [ ] **CI watch loop** (僅在你之前用 `gh pr checks --watch` 輪詢時相關)。發 `watch_ci(repo, branch)`,確認 CI 結束時自動注 inbox event,不需 agent 自己輪詢。 @@ -674,7 +674,7 @@ git worktree add ../my-repo.worktrees/ 這些不是「移除」—— 是 Rust 目前缺 TS 功能、且有計畫補 (或硬化現有行為) 的項目。**不要** 依賴 *目前* 行為跨過下面所列的里程碑: - **`outbound_capabilities` 兩階段 transition。** Sprint 22 P0 給「warn-but-permit one daemon cycle」grace;**Sprint 23 把 absent 升為 hard parse error**。Sprint 23 ship 後,operator 自寫的每個 instance 必須在 `fleet.yaml` 明確宣告 `outbound_capabilities`,不然 daemon 拒絕載入。Built-in coordinator (`general` 與未來自動建立的 coordinator) 自動注入 `[reply, react, edit, inject_provenance]`;user-authored entry 不會自動注入。詳見 [Phase A High-friction #3](#fleet-yaml-schema-diff)。 -- **PTY transport / signal capability matrix。** [§4 信號與 ESC byte 語意](#backend-invocation-diff) 把四個 backend 的 `interrupt` / `tool_kill` 語意標 `pending`。Rust 專案 Sprint 11 會做 real-CLI 驗證;在那之前,對應 cell 上的 semantic 主張請當作未驗證。 +- **PTY transport / signal capability matrix。** [§4 信號與 ESC byte 語意](#backend-invocation-diff) 把四個 backend 的 `interrupt` / `tool_kill` 語意標 `pending`。Real-CLI 驗證目前以 backlog item 形式追蹤,filed in Sprint 11 (`t-20260425040356199333-6`);Sprint 22-25 roadmap 沒 commit 完成這項工作的特定 sprint window,且 operator 對「這項工作是否還值得做」本身在 review。對應 cell 上的 semantic 主張請當作未驗證。 - **`AGEND_TOOL_SET` profile。** TS 透過 `AGEND_TOOL_SET` 開放 `standard` (12-tool) 與 `minimal` (4-tool) tool profile (`src/channel/mcp-tools.ts:120-126`)。Rust 目前對所有 spawn 出來的 agent 都暴露完整 set (約 45 個工具)。如果你之前用 `minimal` 降低 per-instance token overhead,Rust 上 token 用量會比較高,直到 profile 機制實作。Tracked as follow-up;沒有 committed 的 Rust release。 - **跨 channel 架構。** `channel.user_allowlist` 與 `outbound_capabilities` 兩個 gate 目前以 Telegram 為主。Discord/Slack adapter 在 channel parity 完成後會透過 `auth.rs::gate_outbound_for_agent` 繼承同一組 gate;在那之前,各 channel 行為可能仍有落差。`agend-terminal` 的 `docs/MIGRATION-OUTBOUND-CAPS.md` operator 完整參考有跨 channel 架構備註。 - **`list_instances` 的 `tags` filter** (TS-only)。[Phase B §5.2](#mcp-tool-api-diff) 標記為 open question —— TS `list_instances` 接 `tags` filter,Rust 目前不接參數。如果你依賴 tag-filter 的 instance 列舉,在 parity 補上前請改手動列。 From a9509deb6c5b05fc39d90d288fb6f1bf2bf518d2 Mon Sep 17 00:00:00 2001 From: suzuke Date: Mon, 27 Apr 2026 13:20:09 +0800 Subject: [PATCH 3/3] =?UTF-8?q?docs(migration):=20correct=20delegate=5Ftas?= =?UTF-8?q?k=20busy-gate=20PR=20citations=20(#197/#199=20=E2=86=92=20#149/?= =?UTF-8?q?#161)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dev-reviewer-2 cross-team observation on PR #66 reviewed_head 1fc19d4d caught a wrong-PR citation in the §6 step 6 cross-instance dispatch busy-gate note. The original ts-reviewer O1 finding cited PR #197/#199; I carried that wording through unchanged. dev-reviewer-2's git blame on `src/mcp/tools.rs:50-53` (agend-terminal main) shows the correct provenance: - Rust PR #149 (Sprint 8: "delegate_task busy gate + interrupt=true + timeout stuck detection") — original BUSY-by-default behaviour. - Rust PR #161 (Sprint 10: "rename delegate_task interrupt→force + backwards-compat") — `force`/`force_reason` field rename. - PR #197 (Sprint 18 PR-AY: Task Board UX Phase 2) — unrelated. - PR #199 (Sprint 18.5 HOTFIX: fleet-update PTY submit_key + fleet_events.jsonl write) — unrelated. Fix: replace `#197/#199` with explicit `#149` (busy gate) plus `#161` (force/force_reason rename) so a future operator searching for the behaviour lands on the right PRs. Semantic content of the bullet (BUSY default + `force: true` override) was already correct; only the citation drifted. EN line 615 + zh-TW line 613, one substitution per file. New commit on top of 1fc19d4 per CLAUDE.md (no amend). Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/migration-to-agend-terminal.md | 2 +- docs/migration-to-agend-terminal.zh-TW.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/migration-to-agend-terminal.md b/docs/migration-to-agend-terminal.md index 37f59102..f7705ec9 100644 --- a/docs/migration-to-agend-terminal.md +++ b/docs/migration-to-agend-terminal.md @@ -612,7 +612,7 @@ Run these before declaring the migration done. Each tests one independent surfac - [ ] **Daemon starts.** `agend-terminal start --detached` (or your preferred launch path). `agend-terminal status` reports the daemon as alive and lists every configured instance. - [ ] **Bot answers basic message.** From an allowlisted Telegram account, send any message. The bound instance receives it (`agend-terminal logs ` or pane scrollback shows the inbound), and replies — meaning `outbound_capabilities` is correctly configured (especially `reply`). - [ ] **Inbound user-allowlist gate.** From a non-allowlisted Telegram account, send a message. Confirm the daemon log shows the inbound being rejected with the user_id stamped (`grep "outbound notify dropped" $AGEND_HOME/daemon.log` — remember `RUST_LOG=debug` if you don't see it). Bot does not reply. -- [ ] **Cross-instance dispatch.** Send `delegate_task` from one agent to another. The recipient sees the `[AGEND-MSG]` system reminder and can drain via `inbox`. `describe_message(message_id=…)` confirms the recipient picked up. Ensure both agents are idle when running this test, or pass `force: true` if you want to exercise the busy-override path — `delegate_task` against a mid-LLM-turn receiver returns BUSY by default (post-PR #197/#199 in Rust). +- [ ] **Cross-instance dispatch.** Send `delegate_task` from one agent to another. The recipient sees the `[AGEND-MSG]` system reminder and can drain via `inbox`. `describe_message(message_id=…)` confirms the recipient picked up. Ensure both agents are idle when running this test, or pass `force: true` if you want to exercise the busy-override path — `delegate_task` against a mid-LLM-turn receiver returns BUSY by default (Rust PR #149 added the busy gate in Sprint 8; PR #161 renamed `interrupt`/`reason` to `force`/`force_reason` in Sprint 10). - [ ] **`set_waiting_on` round-trip.** Have one agent declare `set_waiting_on(condition=…)` and have a second agent `describe_instance()` to verify the field surfaces. - [ ] **Cost-guard pre-check.** If you run with a `cost_guard` configured, deliberately push the target over its daily limit (or stub the `isLimited` value in a test) and confirm the sender gets the cost-guard error string, not silent drop. - [ ] **CI watch loop** (only if you used `gh pr checks --watch` style polling). Issue `watch_ci(repo, branch)` and confirm a CI completion injects an inbox event without the agent polling. diff --git a/docs/migration-to-agend-terminal.zh-TW.md b/docs/migration-to-agend-terminal.zh-TW.md index cbc003b8..e678a341 100644 --- a/docs/migration-to-agend-terminal.zh-TW.md +++ b/docs/migration-to-agend-terminal.zh-TW.md @@ -610,7 +610,7 @@ git worktree add ../my-repo.worktrees/ - [ ] **Daemon 啟動。** `agend-terminal start --detached` (或你慣用的啟動路徑)。`agend-terminal status` 回報 daemon alive 並列出每個 configured instance。 - [ ] **Bot 回基本訊息。** 從 allowlisted 的 Telegram 帳號發任何訊息。綁定 instance 收到 (`agend-terminal logs ` 或 pane scrollback 顯示 inbound) 並回覆 —— 代表 `outbound_capabilities` (尤其 `reply`) 設定正確。 - [ ] **Inbound user-allowlist gate。** 從非 allowlisted 帳號發訊息。確認 daemon log 有對應 user_id 的 inbound 拒絕記錄 (`grep "outbound notify dropped" $AGEND_HOME/daemon.log` —— 沒看到記得 `RUST_LOG=debug`)。Bot 不回覆。 -- [ ] **跨實例 dispatch。** 從一個 agent 對另一個發 `delegate_task`。收件方看到 `[AGEND-MSG]` system reminder,可透過 `inbox` drain。`describe_message(message_id=…)` 確認 pickup。請確保兩個 agent 都 idle 時跑這個測試,或若想驗證 busy-override 路徑就傳 `force: true` —— 對 mid-LLM-turn 收件方發 `delegate_task` 預設會回 BUSY (Rust 端 post-PR #197/#199)。 +- [ ] **跨實例 dispatch。** 從一個 agent 對另一個發 `delegate_task`。收件方看到 `[AGEND-MSG]` system reminder,可透過 `inbox` drain。`describe_message(message_id=…)` 確認 pickup。請確保兩個 agent 都 idle 時跑這個測試,或若想驗證 busy-override 路徑就傳 `force: true` —— 對 mid-LLM-turn 收件方發 `delegate_task` 預設會回 BUSY (Rust PR #149 在 Sprint 8 加入 busy gate;PR #161 在 Sprint 10 把 `interrupt`/`reason` 改名為 `force`/`force_reason`)。 - [ ] **`set_waiting_on` round-trip。** 一個 agent 宣告 `set_waiting_on(condition=…)`,第二個 agent `describe_instance()` 確認欄位浮現。 - [ ] **Cost-guard 預檢。** 若有設 `cost_guard`,刻意把 target 推過日預算 (或在測試中 stub `isLimited`),確認 sender 拿到 cost-guard error 字串而非靜默 drop。 - [ ] **CI watch loop** (僅在你之前用 `gh pr checks --watch` 輪詢時相關)。發 `watch_ci(repo, branch)`,確認 CI 結束時自動注 inbox event,不需 agent 自己輪詢。