-
Notifications
You must be signed in to change notification settings - Fork 2.9k
feat: agent board — share work across independently started agents #9402
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
yiliang114
wants to merge
46
commits into
main
Choose a base branch
from
chore/remove-unwired-agent-view
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
5b45e0a
feat(core): add ask and decision as board items
a38232a
feat(cli): add the board command surface and fleet up
8fb834d
feat(core): make a session board-aware when QWEN_BOARD is set
fada565
docs(users): document the agent board
c80fbbd
fix: repair the blockers a direction audit found
521167a
feat(cli): hand the protocol to agents we did not write
43c85bf
fix(cli): repair fleet layout targeting and the type error the audit …
f1effbd
feat(cli): let an already-running session join a board with /board
20d2a03
feat(cli): add board prune, and stop overstating decision authority
dc6dd33
feat: register participants so peers can be addressed by name
d5b0e9a
feat(cli): poll the board so an idle session stays responsive
ad7c124
feat(cli): show what the board is waiting on in the footer
ea22b21
refactor(core): fold the three copies of the file lock onto one factory
420fac7
feat: task dependencies and the deadlock the design promised
475a477
fix(core): type sharp by its module shape so the package builds
234352d
fix: stabilize agent board fleet startup
yiliang114 dbc623a
chore(cli): use standard command output streams
yiliang114 803a56b
feat(cli): make decision authority structural rather than a request
49c520d
fix(core): restore a callable sharp type in image-view
yiliang114 39db82c
fix(board): guard os.userInfo() in the participant-name fallback
yiliang114 d6c32c1
fix(board): never prune on an unparseable settle timestamp
yiliang114 c4b34f2
Merge branch 'main' into chore/remove-unwired-agent-view
yiliang114 0cd76ea
fix(board): sanitize record-controlled text before terminal rendering
yiliang114 1ed7666
fix(board): parse the block command and reject NaN numeric options
yiliang114 2600978
fix(board): reject a NaN --agents in fleet up
yiliang114 2af44de
fix(board): keep the collision-suffixed name inside SAFE_NAME
yiliang114 b206daf
fix(board): move the mutex-eviction check outside runExclusive
yiliang114 bdb7fbc
fix(board): prune asks that settled as timeout
yiliang114 cac83da
fix(board): age timeout asks by their expiresAt in prune
yiliang114 8116916
fix(board): carry --board/--as in the protocol command examples
yiliang114 78615ac
fix(core): restore a callable sharp type in image-view
yiliang114 148fa61
fix(board): resolve the tsc errors blocking the board build
yiliang114 51226ee
fix(board): satisfy ESLint for the board files
yiliang114 8c95b6f
fix(board): move board exports off the core barrel to keep iconv-lite…
yiliang114 ba1e8bf
fix(board): restore CI coverage
yiliang114 e733e19
fix(i18n): register board command description
yiliang114 69d69ff
fix(board): narrow the collaboration MVP
yiliang114 7280757
fix(board): harden persisted record boundaries
yiliang114 c230178
test(board): satisfy terminal sanitizer lint
yiliang114 6986db4
fix(board): close round-4 findings — keep emit multiline, guard rende…
yiliang114 e7d56c0
Merge remote-tracking branch 'origin/main' into chore/remove-unwired-…
yiliang114 99a86b9
docs: include agent board collaboration design
yiliang114 b04948c
refactor(board): narrow the collaboration surface
yiliang114 271c169
fix(board): keep piped --json intact and validate --timeout before wr…
yiliang114 4f474e6
fix(board): report pruned items by id rather than filename
yiliang114 b47569f
docs(board): clarify collaboration delivery sequence
yiliang114 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,213 @@ | ||
| # Peer session collaboration for Qwen Code | ||
|
|
||
| > Status: Board layer implemented by #9402; not a standalone collaboration milestone | ||
| > Tracking: [#8724](https://github.com/QwenLM/qwen-code/issues/8724) | ||
|
|
||
| ## Decision | ||
|
|
||
| The product path and the portable board are separate layers. The original #8724 milestone is | ||
| Qwen-to-Qwen collaboration: already-running Qwen Code sessions discover each other and use | ||
| `send_message` with a receiver-side consent gate. Agent Team already owns the simpler case | ||
| where one session spawns and coordinates an in-process Qwen teammate. | ||
|
|
||
| Independently started or non-Qwen agents can additionally collaborate through a durable board | ||
| on disk. Every participant uses the same pull-based CLI. Qwen-specific delivery, terminal | ||
| hosting, and UI do not define the board contract, but delivery or orchestration is required | ||
| before the project can claim a user-visible delegation milestone. | ||
|
|
||
| The first release has three rules: | ||
|
|
||
| 1. **No membership.** There is no join, leave, participant record, roster, heartbeat, or name | ||
| claim. Reading or writing a named board is participation. | ||
| 2. **No implicit identity or scope.** Commands require `--board <name>` and mutations require | ||
| `--as <name>`. These values label records; they do not authenticate the caller. | ||
| 3. **Pull is the contract.** Work becomes visible on the board. Nothing is delivered into a | ||
| running agent process. | ||
|
|
||
| This is the smallest storage model shared by Qwen Code, Codex, shell scripts, scheduled jobs, | ||
| and other tools that can run a command. It is not by itself a scheduler, launcher, or inbox. | ||
|
|
||
| ## Product sequence | ||
|
|
||
| The user-visible collaboration route is: | ||
|
|
||
| 1. **Spawned Qwen teammates.** Existing Agent and Agent Team tools cover delegation to a | ||
| Qwen worker managed by the current session. | ||
| 2. **Already-running Qwen sessions.** The session registry discovers peers; the inbound gate | ||
| lets a peer refuse or hold work; sender addressing completes the original #8724 flow. | ||
| 3. **Independent process launch.** A launcher is needed only when a separate long-lived Qwen | ||
| Code process is required instead of an in-process teammate. | ||
| 4. **Foreign runtimes.** A concrete runner, such as a Codex runner, turns one board task into | ||
| a real process invocation and returns its result. | ||
|
|
||
| Stages 1 and 2 of the board implementation below prove the portable storage contract. They | ||
| must not be presented as completion of this product sequence, and the board should not block | ||
| the Qwen-to-Qwen sender path. | ||
|
|
||
| ## Why a board | ||
|
|
||
| An already-running process can receive unsolicited input only if it voluntarily exposes an | ||
| inbound channel. Foreign CLIs do not expose a common one. They can all run a command, so | ||
| fetching shared state is the portable operation. | ||
|
|
||
| Qwen Code already has the pieces needed for a filesystem-backed implementation: | ||
|
|
||
| - atomic JSON writes; | ||
| - `proper-lockfile` for cross-process locking; | ||
| - task ownership and state-transition patterns; | ||
| - process-liveness discovery for features that need it later. | ||
|
|
||
| The board reuses the locking and state-transition patterns. It does not reuse Agent Team's | ||
| storage or scheduler because those are scoped to a spawned, in-process team. | ||
|
|
||
| ## MVP contract | ||
|
|
||
| ### Scope and identity | ||
|
|
||
| `--board` is required on every command. It is a logical name, not a path. The implementation | ||
| validates it before resolving the directory under `~/.qwen/boards/`. | ||
|
|
||
| `--as` is required when creating or changing an item. Read-only commands may omit it unless | ||
| they request an identity-filtered view. The value is written into fields such as `createdBy`, | ||
| `owner`, or `from`. | ||
|
|
||
| There is deliberately no default derived from the current directory, environment variable, | ||
| global process state, or live-session registry. Those shortcuts can be added after one | ||
| unambiguous contract ships. | ||
|
|
||
| ### Storage | ||
|
|
||
| ``` | ||
| ~/.qwen/boards/{board}/ | ||
| tasks/{id}.json | ||
| asks/{id}.json | ||
| ``` | ||
|
|
||
| Directories use mode `0700`; files use `0600`. Each item is one versioned JSON object. | ||
| Identifiers have a type prefix and UUID suffix. Creating an item never scans for or reuses a | ||
| numeric id. | ||
|
|
||
| All read-modify-write transitions and pruning use the same per-item lock discipline. Creation | ||
| uses exclusive semantics. A command must not decide that a target is stale and then mutate | ||
| it after releasing the lock. | ||
|
|
||
| Readers validate each record. A list operation reports and skips malformed records so one | ||
| bad file does not hide the rest of the board. A mutation targeting a malformed record fails | ||
| without rewriting it. | ||
|
|
||
| ### Items | ||
|
|
||
| | Item | Purpose | Terminal states | | ||
| | ------ | ---------------------------------------- | --------------------------------- | | ||
| | `task` | Work with an owner, status, and notes | pending / in_progress / completed | | ||
| | `ask` | A question addressed to a declared label | answered / declined / timeout | | ||
|
|
||
| An `ask` is addressed to a label, not a registered session. A receiver chooses the same | ||
| label with `--as` and answers it. If nobody does, its deadline determines `timeout`; no | ||
| background sweeper is required. | ||
|
|
||
| There is no generic message. Status belongs on a task and information requests are asks. | ||
|
|
||
| ### CLI | ||
|
|
||
| The MVP surface is non-interactive and machine-readable: | ||
|
|
||
| ```text | ||
| qwen board show --board <board> [--as <name>] [--json] | ||
| qwen board task --board <board> --as <name> <subject> [--owner <name>] | ||
| qwen board claim --board <board> --as <name> <task-id> | ||
| qwen board done --board <board> --as <name> <task-id> [--note <text>] | ||
| qwen board ask --board <board> --as <name> <to> <question> [--wait] [--timeout <duration>] | ||
| qwen board answer --board <board> --as <name> <ask-id> <answer> | ||
| qwen board decline --board <board> --as <name> <ask-id> <reason> | ||
| qwen board prune --board <board> --as <name> --older-than <duration> | ||
| ``` | ||
|
|
||
| `--json` produces stable data without ANSI output. Human output may be formatted but must not | ||
| truncate identifiers or state needed to act. `--wait` uses bounded polling, returns a | ||
| distinct timeout exit code, and does not start a daemon or socket. | ||
|
|
||
| Unknown ids, invalid transitions, invalid names, malformed target records, and lock failures | ||
| are errors. An absent board is an empty result for `show` and an error for mutations that | ||
| need an existing item. | ||
|
|
||
| ## Authority and security boundary | ||
|
|
||
| The local OS account is the access boundary. File permissions prevent other local users from | ||
| reading the board. `--as` is self-declared and must never authorize filesystem access, | ||
| dangerous tools, approval mode, or sandbox changes. | ||
|
|
||
| Board text is untrusted input. Consumers display or summarize it as data; they do not inject | ||
| it as a user message or automatically execute instructions from it. | ||
|
|
||
| Push delivery, if added, requires a receiver-side consent gate before the first send path. | ||
| That later gate must fail closed and cannot trust the sender's declared `--as` value. | ||
|
|
||
| ## Board implementation stages | ||
|
|
||
| ### Stage 1 — storage primitives | ||
|
|
||
| Add versioned task and ask records with validation, secure permissions, one lock discipline, | ||
| random ids, and focused transition tests. | ||
|
|
||
| Observable result: two processes can safely create, claim, answer, and prune items on the | ||
| same named board without lost updates or id reuse. | ||
|
|
||
| ### Stage 2 — CLI | ||
|
|
||
| Expose the storage primitives through the explicit `--board` / `--as` commands above. | ||
|
|
||
| Observable result: two independently started agents, including a non-Qwen agent, can share | ||
| work by running commands and can distinguish completed, declined, and timed-out outcomes | ||
| from exit status and JSON. | ||
|
|
||
| ### Stage 3 — Qwen-native consumer | ||
|
|
||
| After the CLI contract is stable, a Qwen-native tool, slash command, footer indicator, or | ||
| turn-boundary poll may consume it. Every native action must map exactly to an existing CLI | ||
| operation. | ||
|
|
||
| Observable result: Qwen users get lower-friction access without changing board semantics or | ||
| excluding foreign agents. | ||
|
|
||
| ### Stage 4 — orchestration or push | ||
|
|
||
| Fleet/tmux startup, a Codex runner, and Qwen-to-Qwen wake delivery are separate features. | ||
| Fleet may pass explicit board and identity arguments to child commands; it does not create | ||
| membership. Push lands only with its receiver-side consent gate and remains a latency | ||
| optimization for board correctness, while still being necessary for low-latency delegation | ||
| to an already-running Qwen session. | ||
|
|
||
| Observable result: a user can ask a lead agent to start or address a worker and receive its | ||
| result without manually operating the board CLI. Removing this stage leaves the Stage 2 | ||
| storage contract correct, but removes the user-visible delegation experience. | ||
|
|
||
| ## Explicit non-goals for the MVP | ||
|
|
||
| - membership, participant records, join/leave, roster, name claiming, or liveness coupling; | ||
| - implicit project boards, ambient identity, or process-global board context; | ||
| - `/board`, footer badges, background polling, or native agent tools; | ||
| - fleet/tmux orchestration, PTY attachment, or lifecycle management; | ||
| - push, broadcast, remote access, or cross-machine synchronization; | ||
| - multiple agents writing the same checkout; | ||
| - a public compatibility promise for the on-disk format. | ||
|
|
||
| The existing agent-view PR series (#7799–#7803) owns supervised terminals and roster UI. It | ||
| is independent of this board and is neither removed nor extended by the MVP. | ||
|
|
||
| ## Board implementation acceptance gate | ||
|
|
||
| The MVP is ready only when: | ||
|
|
||
| - its implementation contains no participant or join/leave subsystem; | ||
| - every mutation has explicit board and actor arguments; | ||
| - concurrent creators cannot reuse ids or overwrite each other; | ||
| - pruning cannot delete an item changed after eligibility was checked; | ||
| - malformed records cannot crash or hide a healthy board; | ||
| - human output preserves actionable ids and JSON output remains parseable; | ||
| - focused unit tests cover the above, followed by package typecheck and build. | ||
|
|
||
| Passing this gate means the board implementation is technically sound. It does not by itself | ||
| mean the cross-session collaboration product is complete or that this layer must land before | ||
| the Qwen-to-Qwen sender path. A standalone merge requires an explicit maintainer decision to | ||
| ship the low-level CLI; otherwise it should land with a concrete native consumer or runner. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| # Agent Board | ||
|
|
||
| Agent Board lets independently started agents share work through files on the | ||
| same machine. It does not start, join, monitor, or send input to agent processes. | ||
|
|
||
| It is a low-level interoperability surface, not the Qwen Agent Team scheduler or | ||
| the cross-session messaging transport. A task owner is only a recorded label; | ||
| it does not start or wake a Qwen Code, Codex, or other agent process. | ||
|
|
||
| > Experimental. The on-disk format may change between releases. | ||
|
|
||
| ## Use a board | ||
|
|
||
| Every command names the board explicitly. Every command that changes the board | ||
| also declares the actor with `--as`. | ||
|
|
||
| ```bash | ||
| qwen board task "check the API response" --board orders --as api | ||
| qwen board show --board orders | ||
| ``` | ||
|
|
||
| The first command prints a task id. Another agent can claim and complete it: | ||
|
|
||
| ```bash | ||
| qwen board claim <task-id> --board orders --as web | ||
| qwen board done <task-id> --board orders --as web --note "status is numeric" | ||
| ``` | ||
|
|
||
| `--as` is a label recorded with the action, not authentication. There is no | ||
| membership list, join command, heartbeat, or reserved participant name. | ||
|
|
||
| ## Ask a question | ||
|
|
||
| ```bash | ||
| qwen board ask web "does the client parse status as text?" \ | ||
| --board orders --as api --wait | ||
| ``` | ||
|
|
||
| The receiver uses the same label when answering or declining: | ||
|
|
||
| ```bash | ||
| qwen board answer <ask-id> "yes" --board orders --as web | ||
| qwen board decline <ask-id> "not my area" --board orders --as web | ||
| ``` | ||
|
|
||
| With `--wait`, exit code `0` means answered, `2` declined, `3` the ask's TTL | ||
| expired, and `4` the local wait ended while the ask was still open. `--timeout` | ||
| sets the local wait in seconds; `--ttl` sets the ask lifetime in seconds. | ||
|
|
||
| ## Machine-readable output | ||
|
|
||
| Add `--json` to receive JSON without ANSI formatting: | ||
|
|
||
| ```bash | ||
| qwen board show --board orders --as web --json | ||
| ``` | ||
|
|
||
| Passing `--as` to `show` filters tasks to that owner and asks to or from that | ||
| actor. | ||
|
|
||
| ## Housekeeping | ||
|
|
||
| Settled records remain until explicitly pruned: | ||
|
|
||
| ```bash | ||
| qwen board prune --board orders --as human --older-than 7 | ||
| ``` | ||
|
|
||
| The cutoff is in days. Pruning rechecks each record while holding its lock, so | ||
| an item changed after the scan is not deleted from stale information. | ||
|
|
||
| ## Limits | ||
|
|
||
| - Boards live under `~/.qwen/boards/` and are scoped to the current OS user. | ||
| - Nothing is pushed into an agent. Each participant chooses when to read. | ||
| - Board text is untrusted data and is never automatically executed. | ||
| - Multiple agents writing the same checkout is not supported. | ||
| - Slash commands, footer polling, fleet/tmux orchestration, and remote boards | ||
| are not part of this first version. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.