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

Updated 2026-10-17

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

#FindingEvidence
F1Zero parameter-level descriptions. 209 description: fields = exactly one per tool; every parameter is a bare { type: 'string' }.tools.ts grep
F2One 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
F3Errors 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
F4Pass-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
F5Boot 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
F6Upsell 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
F7Wire 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
F8Guidance 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
F9UI-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
F10Descriptions 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)

  1. 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).

  1. 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).

  1. additionalProperties: false on every inputSchema, defaults declared in-schema.
  2. One vocabulary for shared params — projectId, query, limit mean 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)

  1. 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+).

  1. Split _hello by 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).

  1. Role-filter everything in the boot payload — today toolNamespaces and tool

counts include tools the caller cannot invoke; an agent that plans around a tool it can't call wastes its only shot.

  1. Make recommendedWorkflow conditional, not linear: "no project → intake/fixtures;

project planned → review; artifacts generated → validate; validated → deploy." Three branches beat 24 ordered steps.

  1. Serve title and MCP annotations on 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)

  1. 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.

  1. Every workflow tool's structuredContent carries nextAction

({ 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.

  1. Move the upsell out of mid-task results. Restrict the footer to _hello and

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 _hello currently 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:

  1. Name grammar + uniqueness + sentinel rule (from TOOL_NAMING_STANDARD.md).
  2. Every parameter has a non-empty description.
  3. String params whose description lists fixed options must declare enum.
  4. additionalProperties: false present.
  5. Tool description ≤ 350 chars and contains no banned UI vocabulary

(modal, panel, ASCII, UX).

  1. Served strings (prompts, quickStart, criticalRules, elevation hints) contain no

dotted tool names.

Read this document as markdown — the same bytes an agent receives.