Why we shipped search-users before Phase 3 finished

Why we shipped search-users before Phase 3 finished

Ask Q Phase 3 ships in pieces. The piece that shipped today is search-users for the chatter lane — the boring fix that ends a class of agent failure: pasting UUIDs into a Tasks call when the operator only knew the assignee by first name.

The composer chips behind Phase 3 — the big-paste plumbing, the polish on the multi-line prompt — aren't landing this week. The fix that does ship this week is smaller, narrower, and more useful than any of that: chatter now has a real lookup tool to find people in Tasks. Until Phase 3 drops fully, this is the win operators feel.

Why agents fail without people lookup

Agents fail at the people step in roughly the same way every time. The operator says "assign this to Sam in compliance." Sam is a person, not a UUID. The agent does not have a UUID. The agent has a name, a role, and a vague memory of last week's interaction. Without a real lookup, the agent guesses, pastes the wrong ID, the call returns a no-op, and the operator has to find the ID themselves and paste it back.

The loop is dumb, and worse, it's avoidable. search-users is the fix for it.

What ships: search-users in SMCP v0.4.0 chatter

The shipping surface is the SMCP plugin v0.4.0 chatter lane — Q Vernal's chatter-facing tool surface. The new tool is q_vernal_tasks__search-users, sitting at tool #16 in the chatter profile's 16-tool fall-set. It backs onto GET /api/search-users.php, an API-first endpoint on Tasks. The chatter profile is the same narrow 16-tool set Q has been auditing and tightening since Phase 1; chatter doesn't get a list-users admin dump, chatter gets a prefix-search.

The shape of the call: chatter passes a partial token, the API returns candidates with id, display name, and a stable canonical handle. Chatter picks the right one, resolves username → id, and continues with the original create/update call. The Q Vernal job rule enforces the order: resolve first, mutate second. The rule is a tool-profile rule. It is enforcement, not suggestion.

The wrapper that attaches the right tools to the right profile is moya_attach_q_smcp.py — the same wrapper as before, the same attachment semantics, with search-users now wired in. If you read the prior companion post on ask-q-vernal-webchat hardening for all-day use, that work was the bridge: chatter got narrow, tools got audited, search-users is the next sharpening pass.

If you're running an agent that was pasting UUIDs into a Tasks call, the new shape is: chatter says "Sam in compliance" → chatter calls search-users → chatter resolves the right id → chatter proceeds. The dumb loop is gone.

Why prefix-search and not a list-dump

There's a temptation at this layer to expose "list all users" because it sounds simple. The list-dump is the wrong product. A list-dump lets the chatter agent enumerate every user in the org regardless of relevance, which is the leak surface we're building chat agents specifically to avoid. We are not going to ship chat agent tooling that lets the agent dump the whole user table into its context. The blast radius on a chat agent that suddenly has to render a list of 800 internal handles into a prompt is not a problem the chat agent can recover from.

search-users is prefix-only. The caller passes a partial token. The API returns only that. The query shape is bounded. The blast radius is bounded.

The boring reason search-users.php lives at /api/ and is not a wrapper around a Tasks admin endpoint: we have a soft rule that chat agent tooling reads from /api/, not from admin HTML, not from the database directly. The rule is what keeps the chat agent from being functionally indistinguishable from a legitimate user with a stolen cookie. The rule ships with the tool.

Chatter profile and the operator day

The chatter profile is the narrow 16-tool surface that Q attaches at chat-agent runtime. search-users is one of those 16. The wrapper moya_attach_q_smcp.py is the seam that turns "what tools are available to this agent" into a governed surface, not an org-wide catalog.

Two things matter here that operators don't always see.

The first is that search-users is attached, not default. The chatter profile is shipped to all chat-agent runtimes, but a runtime that wants search-users declared-explicitly has to attach it through the same attach/detach discipline used on the rest of the SMCP surface. Operators who have been working through the Sanctum Tasks / SMCP posts will recognize the architecture: the protocol decides what a chat agent sees; the chatter profile is the concrete shape the protocol takes on go-live. We didn't invent a separate governance story for search-users. We wired it onto the story that already shipped.

The second is the order-of-operations rule. Resolve username → id, then call create/update. That is enforced. A chatter agent that calls create/update with a display name and no resolved id gets back a tool hint that says "use search-users first." That hint is the mechanism by which the chatter profile nudges agents toward the right next action — without forcing a developer to hand-hold the loop.

What changes for the operator running Sarah, our chat-agent persona, through a Tasks-intensive day — assigning a deal handler, pinging a reviewer, fixing an assignee typo — is one fewer dumb loop. Yesterday: Sarah says "I need a UUID." The operator says "go find it." The operator opens Tasks, copies the id, pastes back. Today: Sarah calls search-users, gets the right candidates, resolves, and proceeds. The operator never left the chat.

The fix is small enough that an operator won't write a hero post about it. That is exactly the kind of fix we want from a Phase 3-adjacent ship. The fix that matters is the one whose absence was loudest. Agent guessing was loud. The fix is quiet.

What's in flight

There are limits I won't pretend around. Phase 3 in full — composer large-paste chips, multi-line prompt affordances, higher-confidence Q&A revisions — is still running through review. They will land when they are ready, not on a calendar. search-users is the part operators feel this week, and search-users is what this post is about.

The chatter profile remains a 16-tool surface; an audit we owe ourselves in the next sprint is whether prefix-search has the right token-shape now that it has shipped. We have a regression test for the false-positive case on the "use search-users first" tool hint, and we will keep it current as job rules tighten. The wrapper that attaches the tool profile is the same wrapper that handles attach/detach on the broader SMCP protocol. If that surface ever grows in scope, the rule we want to keep is the boring one: chatter reads /api/; chatter does not see the admin dump; chatter resolves identifiers through the same wire that everything else does.

We're not claiming Phase 3 is done. We're claiming search-users is shipped, chatter has it, the resolve-before-mutate rule holds, and the dumb loop on agent-assignee is over. The next composer chip is the next composer chip. The boring fix is what shipped today.