Skip to content

[codex] make the dashboard Work-first - #82

Merged
jiunbae merged 10 commits into
mainfrom
feat/work-domain-v2
Aug 24, 2026
Merged

[codex] make the dashboard Work-first#82
jiunbae merged 10 commits into
mainfrom
feat/work-domain-v2

Conversation

@jiunbae

@jiunbae jiunbae commented Aug 24, 2026

Copy link
Copy Markdown
Member

What changed

  • Introduces a durable Work domain separate from external issues, runs, agents, and tmux topology.
  • Rebuilds the web Work Board around workspace and Work progress, with execution hierarchy available only as expandable detail.
  • Adds Work metadata, external issue references, workflow stages/signals, start/prompt/status/abort controls, and unlinked execution diagnostics.
  • Reworks muxa dashboard into a Work-first CLI board while retaining exact run/pane controls.
  • Updates CLI terminology, configuration, architecture, dashboard, pipeline, MCP, watch, and workspace documentation.

Why

The previous dashboard treated tmux windows and agents as tickets, which blurred durable work, provider issues, and transient execution. This change makes Work the operator-facing unit while preserving session/window/pane topology as an implementation detail that can still be expanded and controlled.

Impact

  • Operators can assess progress by workspace and Work rather than scanning a topology tree.
  • Linear, GitHub, Jira, and other issues remain linked external references instead of becoming agent or window identities.
  • Batch controls target only live agent panes and retain exact host/socket/pane identity.
  • Legacy window-key metadata is migrated and remains compatible.

Self-review fixes

  • Reject ambiguous API requests that provide both a logical Work identity and a legacy execution key.
  • Clear stale optional provider fields when an external issue link is refreshed.
  • Pin CLI prompt, abort, capture, attach, and batch actions to the exact execution endpoint.

Validation

  • cargo fmt --all -- --check
  • cargo test --workspace (718 library passed, 1 ignored; 705 CLI passed; 12 E2E passed; 70 daemon passed)
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • node --test crates/muxa/tests-js/*.test.mjs (6 passed)
  • pre-push fmt and Clippy hook passed

Jiun Bae and others added 10 commits August 24, 2026 11:40
The dashboard could steer work it had not started: prompt a ticket's
panes, abort them, annotate them. It could not create the team. Meanwhile
`muxa work up` could create the team but had no presence in the UI. This
closes both halves.

`POST /api/work-control/up` runs the pipeline, delegating to the `muxa`
binary rather than reimplementing it. The launcher and the managed-tmux
registry live in the CLI crate, which depends on this one, so calling
them in-process would mean inverting that dependency or keeping a second
implementation alive — and this library deliberately holds a boundary the
move would have breached: no clap, no anyhow, typed errors only. One
implementation behind a subprocess beats two behind none.

It is gated on `[dashboard] allow_work_start` *in addition to* the control
token. Every other write route steers a process the operator already
started; this one starts new ones with permissions bypassed. That is a
different kind of authority, so it gets its own grant, the way `[ask]`
and `[collaboration]` do. Off by default.

`work-control/prompt` now also accepts skill/body/context and runs them
through `muxa::request::compose` — the same composer behind
`muxa_call_peer` and `muxa work up`. A `/name` template registered once
now expands from all three front doors. Literal `text` stays as the
escape hatch, and a request carrying none of the four is refused rather
than sending an empty line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The dashboard asked an operator to type a title the ticket resolver had
just fetched — two sources of truth for the same window, drifting from
the moment either was edited.

A work item created through `/api/work-control/up` now takes its title
from the resolved ticket, but only when the store holds no record for
that window. A title someone typed is theirs and a later run must not
overwrite it, which is the same rule that keeps the pipeline from
touching a pane it did not create. The response reports whether anything
was written, so the UI never has to guess.

It seeds here rather than in the CLI because `WorkStore` rewrites the
whole file on every save: a second writer outside the daemon's mutex
would drop whatever the dashboard held in memory. The consequence is that
a CLI-initiated `muxa work up` still leaves the title empty — closing
that needs an IPC route, not a second writer.

The keying and the overwrite rule are split into `work_key_for_window`
and `already_annotated` so both are tested without a live pane scan: a
window id alone is not a key (the same `@3` exists on every tmux server),
and an existing record blocks seeding for its own window only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The dashboard has a stage axis the CLI never had — queued, in_progress,
review, blocked, done — so the same work item read as "two agents in a
window" on one surface and "waiting on review" on the other. `muxa work
list` and `muxa work show` now print `stage=` when someone has set one.

Reading it needs an identity the CLI did not carry: the store keys on
(host, socket, session_id, window_id), while `WorkInfo` had only the
mutable session *name*. `session_id` was already parsed and thrown away,
so it is now kept, and the socket basename is asked of tmux rather than
read from `$TMUX` — that way it is right outside a pane and under
MUXA_TMUX_SOCKET scoping too.

Read-only on purpose. `WorkStore` rewrites the whole file on save, so a
second writer outside muxad's mutex would drop records the dashboard
holds in memory.

`stage=auto` prints nothing: auto means nobody has said anything, and a
column that is always there says less than one that appears when it has
something to report.

Verified end to end against an isolated tmux server and an isolated
XDG_DATA_HOME: a matching record shows `stage=review`, and changing the
socket in that record makes it stop matching — the same `@2` on another
server is not this work item.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The endpoint landed without a way to reach it from a browser. The work
board header now carries a work-id field, an optional instruction, and a
"start work" button; the button reports what happened (`2 started`,
`2 prompted`, or `already staffed`) and refreshes the board.

Wiring the UI turned up a real defect in the gate. `controlFetch` reads
401/403 as "your token is bad" and flips the whole dashboard to
read-only, so refusing a disabled capability with 403 would have told the
operator their PAT was broken. The refusal is now 501 — the same status
the backend capability checks already use for "this deployment cannot do
that" — and the test says why so it does not drift back.

It also says the button is slow on purpose: resolving a ticket spends a
headless agent turn, so the button disables itself and reads "starting…"
rather than looking dead.

Docs: the `allow_work_start` switch in config.example.toml, the endpoint
and its reasoning in DASHBOARD.md, and a bidirectional section in
PIPELINE.md/.ko.md — the board can start a pipeline, and the pipeline's
ticket title and the board's workflow stage now cross to the other
surface.

Verified end to end against an isolated daemon, tmux server, and
XDG_DATA_HOME: the served HTML carries the form; a first call resolves
the ticket, launches two panes, and reports `seeded_metadata: true`; a
second call launches nothing, prompts both, and seeds nothing; setting
`stage=review` from the dashboard makes `muxa work list` print it for
that window and nothing for the window with no record.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jiunbae
jiunbae marked this pull request as ready for review August 24, 2026 09:35
@jiunbae
jiunbae merged commit 6c8069f into main Aug 24, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant