Agent ergonomics
What an agent should expect from this surface: next actions, terminal answers, depth, and how a call tells you what to do next.
markdownguidev1JS1Q-RBCNergonomicsagentsnext-actiondepth
MCP Agent Ergonomics — second-pass analysis & improvements
Companion to TOOL_NAMING_STANDARD.md. Goal: make Sonnet- and Haiku-class agents
succeed on first attempt without deep reasoning — the tool surface itself should do
the thinking. Findings are from a full read of mcp/handler.ts (1,138 lines),
mcp/tools.ts (209 defs), and services/hello.ts.
Measured findings
| # | Finding | Evidence |
|---|---|---|
| F1 | Zero parameter-level descriptions. 209 description: fields = exactly one per tool; every parameter is a bare { type: 'string' }. | tools.ts grep |
| F2 | One enum in the entire catalog, despite ≥8 closed-set parameters (export profile, job type, override type, secret kind, report kind, CRUD collection, pattern status, source type). | tools.ts grep |
| F3 | Errors don't teach. "projectId is required.", "Pattern not found." — no recovery path. (Exception that proves the rule: account.elevate's email error includes a copy-pasteable example and is excellent.) | handler.ts:198,594,1016,1085 |
| F4 | Pass-through state objects. ~10 tools require modelPlan / entityPlan / workflowPlan: {type:'object'} — the agent must fetch a large JSON and echo it back intact. Small models mangle round-tripped objects. | tools.ts:41–48,52–54,58 |
| F5 | Boot payload is oversized and partly human-themed. _hello returns ASCII banners, progress bars, "Choose your adventure"; agentBootContext.toolNamespaces dumps all 209 tools with full descriptions, unfiltered by role; 14 dense criticalRules; a flat 24-step recommendedWorkflow with no conditions. | hello.ts:35–148 |
| F6 | Upsell footer is injected into tool results mid-task (2nd call, then every 5th) for free tiers. Small models sometimes respond to injected marketing instead of continuing the task. | handler.ts:77–88,138 |
| F7 | Wire response omits title, outputSchema, and MCP tool annotations — only name/description/inputSchema are served. initialize returns no instructions and pins protocolVersion: '2024-11-05'. | handler.ts:118,121 |
| F8 | Guidance is internally inconsistent. Prompts/quickStart reference alias names (semarchy.catalog.search_patterns → actually runs list_patterns; semarchy.deploy.upload_csv_data), while criticalRules reference the flat names. serverInfo.version (0.25.1) ≠ hello version (0.24.0). | handler.ts:66–73, hello.ts:41–49,150 |
| F9 | UI-speak in descriptions. "for right-panel preview or full-screen edit modal", "rich ASCII job telemetry" — meaningless or misleading to an agent deciding whether to call. | tools.ts:163–165,133 |
| F10 | Descriptions say what, almost never when — no trigger conditions, no "typically follows X", no "don't use for Y". | catalog-wide |
Improvements, in priority order
P1 — Schemas that remove choices (biggest small-model lift)
- Enum every closed set. A Haiku given
profile: {type:'string'}invents
"seml"; given enum: ['accelerator','seml_only','docs_only','validation','prompt_pack','deployment']
it cannot be wrong. Apply to: workspace-export_profile.profile,
jobs-queue.type, report-submit.kind, security-store_secret.kind,
model-add_override.type, crud-*.collection, learning-list_patterns.status,
docs-add_source.type, data-browse.collection (if listable at boot).
- Describe every parameter, with an example value in the description.
projectId: { type:'string', description: "Modeling project id, e.g. 'project-G17BOeBt'. List with semarchy-project-status." }
This single pattern collapses the two most common small-model failures
(wrong format, wrong source of the value).
additionalProperties: falseon every inputSchema, defaults declared in-schema.- One vocabulary for shared params —
projectId,query,limitmean the same
thing everywhere (already mostly true; lock it in the validator).
P2 — Stop making agents carry state (F4)
Every tool that takes modelPlan/entityPlan/workflowPlan objects should accept
projectId and load the stored plan server-side (the server already persists plans per
project). Keep the object param as an optional override for advanced callers. The agent
contract becomes: "identifiers travel; documents stay on the server." This is the
single biggest reliability win for Haiku-class models in the generation pipeline.
P3 — Errors that contain the next action (F3)
Standardize every thrown error to: what's wrong → what to call instead → example.
projectId is required. Call semarchy-project-status {} to list projects,
or semarchy-fixtures-create_demo {"fixtureId":"..."} to create one.
- "Not found" errors list 2–3 valid candidates when cheap to compute.
- Unknown-tool errors (post-rename) suggest the nearest valid name (levenshtein over
the 172 names is ~free) — this also softens the dotted→dash cutover.
- Role-denial errors already do this well (
elevationHint) — extend the pattern.
P4 — Descriptions that say when, not just what (F9, F10)
Rewrite descriptions in the form capability + trigger + sequencing, ≤ 2 sentences:
Before: "Browse a data collection as rows suitable for list-table plus right-panel detail UX." After: "Browse rows of a local data collection (projects, jobs, audit, …). Use when the user asks what data exists; use semarchy-data-read_record to open one row."
Prescriptive "use when / use after / don't use for" clauses in descriptions give a measurable lift in correct-tool selection for conservative models. Strip all UI-speak. The rename pass touches every definition anyway — do both edits in the same sweep.
P5 — Boot diet + protocol upgrades (F5, F7)
initialize.instructions: add the 3-sentence boot pointer ("Call
semarchy-_hello first…"). Agents receive it before listing tools — the strongest
first-tool guarantee available. Bump protocolVersion negotiation (2025-03-26+).
- Split
_helloby audience. Default (agent) response: role, project count,
conditional next step, ≤6 critical rules, domain list (names only) — target < 1.5K
tokens. The ASCII art/banners move behind format: "visual" (the intent param
already exists; today it does nothing).
- Role-filter everything in the boot payload — today
toolNamespacesand tool
counts include tools the caller cannot invoke; an agent that plans around a tool it can't call wastes its only shot.
- Make
recommendedWorkflowconditional, not linear: "no project → intake/fixtures;
project planned → review; artifacts generated → validate; validated → deploy." Three branches beat 24 ordered steps.
- Serve
titleand MCPannotationson tools/list:
readOnlyHint: true on all dashboards/list/get tools (~half the catalog),
destructiveHint: true on deploy/upload/update/store, idempotentHint where true.
Clients gate dangerous calls and models get a free safety signal.
P6 — Result hygiene (F6 + token economy)
- Lead the text result with one plain sentence (outcome + next step); keep
structuredContent as the machine-readable truth. ASCII boxes/bars/trees are
decoration that costs tokens and adds no information for a model — reserve them for
format:"visual" or drop in agent context.
- Every workflow tool's
structuredContentcarriesnextAction
({ tool, args, reason }) and blockers[]. A small model then runs the entire
factory pipeline as a follow-the-pointer loop — semarchy-project-next_action
already computes this; surface it on every step's result instead of requiring a
separate call.
- Move the upsell out of mid-task results. Restrict the footer to
_helloand
cuppy-account-* responses, or put it in structuredContent.upgradeHint where it
can't be mistaken for task content. Keep the (good) elevation hints on role-denied
errors.
P7 — Consistency sweep (F8)
- All prompts, quickStart entries, criticalRules, and docs reference the **new canonical
names only** (this falls out of the rename, but add a CI grep that fails on
semarchy\. / cuppy\. / account\. in served strings).
- Single source for the version string (serverInfo and
_hellocurrently disagree).
P8 — Optional: a "core" surface for small models
172 tools is fine for Opus-class agents; it's working memory pressure for Haiku. The
role tiers already trim (public = 11). If you want more: add an opt-in per-key
toolProfile: 'core' that serves only the ~30 spine tools (hello, account, project,
flow, model, validation, workspace read, docs search, report) — everything else remains
reachable on the full profile. Cheap to implement (one field on api_keys, one filter
in tools/list); defer until you see small models flailing on selection.
Validator additions (extends the naming validator)
Registration-time checks, server refuses to boot on violation:
- Name grammar + uniqueness + sentinel rule (from TOOL_NAMING_STANDARD.md).
- Every parameter has a non-empty
description. - String params whose description lists fixed options must declare
enum. additionalProperties: falsepresent.- Tool description ≤ 350 chars and contains no banned UI vocabulary
(modal, panel, ASCII, UX).
- Served strings (prompts, quickStart, criticalRules, elevation hints) contain no
dotted tool names.