Skip to content

Add read-only desk tools for agents - #2594

Merged
pournasserian merged 1 commit into
v2from
2591-desk-tools
Oct 19, 2026
Merged

pournasserian merged 1 commit into
v2from
2591-desk-tools

Conversation

@pournasserian

Copy link
Copy Markdown
Member

Closes #2591

What

The sixth slice of F-DSK (#2422): read-only desk tools for agents (MCP-01, "DSK (read-only for review decisions)"), on the tool conventions from #2508. It fills the DSK row of McpCoverageTests.

  • DeskTools (FluentCMS.Core.Builtin.Tools), three read-only tools:
    • get_desk_counts: how many items wait in each queue.
    • list_desk_queue: one page of a queue, ordered as the desk orders it.
      • The queues are awaiting_review, checks_failed, stale_facts, recently_published and reverted. Awaiting me is a person's queue, so it is not offered.
      • page and limit are optional (1 to 200, 50 by default), and the result has the total.
      • A set comes with its id, state, rationale, initiator, item count, the set it reverts and its times. A stale fact is shown as list_stale_facts shows it.
    • get_change_set_review: the state and since when, the rejection reason, the set it reverts, and every move in order with who made it and when.
    • Each tool reads its arguments before any service. An unknown queue, a page below 1 and a limit above 200 are argument_invalid. An unknown set is change_set_not_found with a hint.
  • Agents see their own sets only. Hold the agent's credential while counting its caps, and keep refusal events #2580 made an agent read only the change sets it opened (CheckChangeSetReadAsync).
    • ChangeSetListQuery gains OpenedBy, which ChangeSetLists filters on.
    • DeskQueues sets it when the caller is an agent, so an agent's queues and counts hold its own sets. A person's view is unchanged.
    • get_change_set_review reads through GetAsync, so another party's set is refused with agent_not_change_set_owner.
  • ChangeSetMoves (FluentCMS.Core.ChangeSets) names the open and move operations once. ChangeSetLists, the set view and the review tool used to keep their own copies.
  • DeskToolsConformanceTests, and the DSK rows of McpCoverageTests in place of the missing one.
  • CONTRIBUTING names the desk tools in the MCP paragraph.

Why

  • Scoping in DeskQueues, not in each tool. The queues are defined once for the desk and the tools. Applying the read rule where the queries are built keeps the counts and lists consistent, and a later tool cannot forget it. The rule itself stays in AgentPermissions. The list only follows it, as GetAsync does.
  • get_change_set_review next to get_change_set. get_change_set returns the set's content. The review status is the decisions: who moved it, when, and why it was rejected. An agent needs that to follow up on a rejection.
  • No awaiting me for agents. An agent never reviews (MCP-06), so the queue would only repeat awaiting review.
  • Not in this PR: writing decisions (people only, MCP-06), and security events for agents (SEC-08 puts them on the desk for people).

Tests

Unit:

  • DeskQueuesTests: an agent's lists and counts carry OpenedBy as the agent, and a person's carry none.
  • ToolProviderConformanceTests for DeskTools, and McpCoverageTests with the new rows.

Integration over MCP, each on SQLite and PostgreSQL:

  • An agent opens, writes and submits three sets through its tools, which are moved to awaiting review and checks failed. The counts and the queues list those sets in the desk's order with paging and totals. A person's set awaiting review is left out, and nothing is appended to the audit log.
  • The agent reads the review of its rejected set: the reason, its own moves and the person's rejection, with stateSince at the rejection. Another party's set is refused with agent_not_change_set_owner.
  • An unknown queue (awaiting_me), a limit of 500, a page of 0 and an unknown set are refused, the last with a hint.

DeskQueueTests now reads the queues as a person, since the scenario's default caller is an agent. McpAgents.BuiltInTools lists the three new tools.

dotnet build -warnaserror, the full suite with the browser tests, and markdownlint pass locally against a postgres:17 container as well as SQLite. No new package and no new project reference.

Checklist

  • Linked to an issue a maintainer has confirmed
  • Targets v2
  • Tests or docs updated where behaviour changed
  • No tool-generated co-author trailers or footers

@pournasserian pournasserian added this to the M1 Governance core milestone Oct 19, 2026
@pournasserian pournasserian added type:feature New capability v2 FluentCMS v2 (v2 branch) area:desk Review desk (DSK) labels Oct 19, 2026
@pournasserian
pournasserian merged commit e6d5849 into v2 Oct 19, 2026
2 checks passed
@pournasserian
pournasserian deleted the 2591-desk-tools branch October 19, 2026 06:46
pournasserian added a commit that referenced this pull request Oct 19, 2026
…ts they see their own sets (#2648)

Closes #2625

## What

Follow-ups from the independent review of #2594 (F-DSK, #2422). No
scoping leak was found; these close the test gaps and tighten what
agents are told.

- **Tool descriptions.** `get_desk_counts`, `list_desk_queue` and
`get_change_set_review` say an agent sees only the change sets it
opened, so a count of 0 doesn't read as an empty desk.
- **`get_change_set_review`** reads the set and its moves again when the
last move disagrees with the set's state, up to three times. A decision
that commits between the two reads no longer gives an answer that
disagrees with itself.
- **`ChangeSetMoves.StateAfter`** names the state each move leaves a set
in. The review tool uses it, and a test ties it to the directory.
- **`ConnectedAgent.AnotherAgentAsync`** connects a second agent to the
same host for tests.

## Why

- **Read again rather than derive the state from the moves.** The
rejection reason lives on the set, not in the move event, so only a
fresh read of both gives a consistent answer.

## Tests

On SQLite and PostgreSQL:

- **Another agent's sets** are not counted, listed or readable over MCP,
while that agent sees its own. This exercises the credential-id half of
the `OpenedBy` filter.
- **Every queue through `list_desk_queue`:**
- `recently_published` lists newest first and leaves out a set published
eight days earlier.
  - `reverted` lists the reverted set.
- `stale_facts` pages two and then one of three, with the total on both
pages.
  - The counts match.
- **No tool call appends an event,** `get_change_set_review` included.
- **Every move the directory makes** appends exactly one known move, and
`StateAfter` names the state the move left. The set goes through:
  - open, submit, checks failed, reopen, submit and awaiting review;
  - approve and publish;
  - a published revert, which reverts the original;
  - a rejection.
  
  All nine move names are seen.

`dotnet build -warnaserror`, the unit and bUnit suite and the desk, desk
tools and queue tests on SQLite and PostgreSQL pass locally. No new
package and no new project reference.

## Checklist

- [x] Linked to an issue a maintainer has confirmed
- [x] Targets `v2`
- [x] Tests or docs updated where behaviour changed
- [x] No tool-generated co-author trailers or footers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:desk Review desk (DSK) type:feature New capability v2 FluentCMS v2 (v2 branch)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Read-only desk tools for agents: queues, counts and review status

1 participant