Skip to content
Open
Show file tree
Hide file tree
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
Aug 18, 2026
a38232a
feat(cli): add the board command surface and fleet up
Aug 18, 2026
8fb834d
feat(core): make a session board-aware when QWEN_BOARD is set
Aug 18, 2026
fada565
docs(users): document the agent board
Aug 18, 2026
c80fbbd
fix: repair the blockers a direction audit found
Aug 18, 2026
521167a
feat(cli): hand the protocol to agents we did not write
Aug 18, 2026
43c85bf
fix(cli): repair fleet layout targeting and the type error the audit …
Aug 19, 2026
f1effbd
feat(cli): let an already-running session join a board with /board
Aug 19, 2026
20d2a03
feat(cli): add board prune, and stop overstating decision authority
Aug 19, 2026
dc6dd33
feat: register participants so peers can be addressed by name
Aug 19, 2026
d5b0e9a
feat(cli): poll the board so an idle session stays responsive
Aug 19, 2026
ad7c124
feat(cli): show what the board is waiting on in the footer
Aug 19, 2026
ea22b21
refactor(core): fold the three copies of the file lock onto one factory
Aug 19, 2026
420fac7
feat: task dependencies and the deadlock the design promised
Aug 19, 2026
475a477
fix(core): type sharp by its module shape so the package builds
Aug 19, 2026
234352d
fix: stabilize agent board fleet startup
yiliang114 Aug 19, 2026
dbc623a
chore(cli): use standard command output streams
yiliang114 Aug 19, 2026
803a56b
feat(cli): make decision authority structural rather than a request
Aug 19, 2026
49c520d
fix(core): restore a callable sharp type in image-view
yiliang114 Aug 19, 2026
39db82c
fix(board): guard os.userInfo() in the participant-name fallback
yiliang114 Aug 19, 2026
d6c32c1
fix(board): never prune on an unparseable settle timestamp
yiliang114 Aug 19, 2026
c4b34f2
Merge branch 'main' into chore/remove-unwired-agent-view
yiliang114 Aug 19, 2026
0cd76ea
fix(board): sanitize record-controlled text before terminal rendering
yiliang114 Aug 19, 2026
1ed7666
fix(board): parse the block command and reject NaN numeric options
yiliang114 Aug 19, 2026
2600978
fix(board): reject a NaN --agents in fleet up
yiliang114 Aug 19, 2026
2af44de
fix(board): keep the collision-suffixed name inside SAFE_NAME
yiliang114 Aug 19, 2026
b206daf
fix(board): move the mutex-eviction check outside runExclusive
yiliang114 Aug 19, 2026
bdb7fbc
fix(board): prune asks that settled as timeout
yiliang114 Aug 19, 2026
cac83da
fix(board): age timeout asks by their expiresAt in prune
yiliang114 Aug 19, 2026
8116916
fix(board): carry --board/--as in the protocol command examples
yiliang114 Aug 19, 2026
78615ac
fix(core): restore a callable sharp type in image-view
yiliang114 Aug 19, 2026
148fa61
fix(board): resolve the tsc errors blocking the board build
yiliang114 Aug 19, 2026
51226ee
fix(board): satisfy ESLint for the board files
yiliang114 Aug 19, 2026
8c95b6f
fix(board): move board exports off the core barrel to keep iconv-lite…
yiliang114 Aug 19, 2026
ba1e8bf
fix(board): restore CI coverage
yiliang114 Aug 19, 2026
e733e19
fix(i18n): register board command description
yiliang114 Aug 19, 2026
69d69ff
fix(board): narrow the collaboration MVP
yiliang114 Aug 19, 2026
7280757
fix(board): harden persisted record boundaries
yiliang114 Aug 19, 2026
c230178
test(board): satisfy terminal sanitizer lint
yiliang114 Aug 19, 2026
6986db4
fix(board): close round-4 findings — keep emit multiline, guard rende…
yiliang114 Aug 20, 2026
e7d56c0
Merge remote-tracking branch 'origin/main' into chore/remove-unwired-…
yiliang114 Aug 20, 2026
99a86b9
docs: include agent board collaboration design
yiliang114 Aug 20, 2026
b04948c
refactor(board): narrow the collaboration surface
yiliang114 Aug 20, 2026
271c169
fix(board): keep piped --json intact and validate --timeout before wr…
yiliang114 Aug 20, 2026
4f474e6
fix(board): report pruned items by id rather than filename
yiliang114 Aug 20, 2026
b47569f
docs(board): clarify collaboration delivery sequence
yiliang114 Aug 23, 2026
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
213 changes: 213 additions & 0 deletions docs/plans/2026-08-18-peer-session-collaboration.md
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.
1 change: 1 addition & 0 deletions docs/users/features/_meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default {
'markdown-rendering': 'Markdown Rendering',
'sub-agents': 'SubAgents',
'multi-agent-coordination': 'Multi-Agent Coordination',
'agent-board': 'Agent Board',
Comment thread
yiliang114 marked this conversation as resolved.
arena: 'Agent Arena',
skills: 'Skills',
memory: 'Memory',
Expand Down
79 changes: 79 additions & 0 deletions docs/users/features/agent-board.md
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.
1 change: 1 addition & 0 deletions packages/cli/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,7 @@ describe('bootstrap import boundaries', () => {
const configSource = readFileSync('src/config/config.ts', 'utf8');
const commandNameByIdentifier = new Map([
['authCommand', 'auth'],
['boardCommand', 'board'],
['channelCommand', 'channel'],
['extensionsCommand', 'extensions'],
['hooksCommand', 'hooks'],
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type BootstrapRoute = 'serve' | 'mcp' | 'help' | 'version' | 'default';

export const TOP_LEVEL_COMMANDS = [
['auth', 'Configure authentication (removed)'],
['board <command>', 'Share work with other agents through a board'],
['channel <command>', 'Manage messaging channels (Telegram, Discord, etc.)'],
['extensions <command>', 'Manage Qwen Code extensions.'],
['hooks', 'Manage Qwen Code hooks (use /hooks in interactive mode).'],
Expand Down
Loading
Loading