Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
144
planned
1,362
shipped

Motir · Work items

MOTIR-2208Done

11.7 The WORK-LOOP operations over `/api/v1` — dispatch, session close-out, expansion, the planning conversation, the activity read, and the three edge projections the shipped reads omit

The operations @motir/cli and third-party integrations need from the public API and cannot get today — and the story that discharges 11.5's own open caveat, which it asked to be answered "with a rung-2 audit of mcpClient.ts's method list against the shipped v1 surface".

That audit ran on origin/main. 10 of the CLI's 19 server operations have no /api/v1 equivalent, and they are exactly the ones motir dispatch / motir auto / motir batch / motir plan are made of. Nothing in 11.111.4 was wrong; those stories delivered the resource surface they scoped. The work-loop verbs were simply never in anyone's scope — and docs/decisions/public-api-conventions.md, five amendments in, does not mention one of them.

⚠️ WHO consumes these — and who deliberately does NOT (Yue, 2026-08-05)

The consumers are the CLI and third-party applications. AGENTS ARE NOT. Claude Code and every other MCP client keep speaking /api/mcp, which is the surface built for them. This story adds a second, differently-shaped door onto the same services for a different kind of caller; it does not move anybody through it.

  • @motir/cli (11.5) is the first consumer, because a published npm package must not depend on a contract that is deliberately allowed to churn.
  • Third-party integrations — an SDK, a CI action, someone's own orchestration script — are the reason the endpoints are public at all.
  • Motir's own agents keep MCP. Not one tool in lib/mcp/tools/ is re-pointed, re-shaped or deprecated. An agent talking to itself through an HTTP hop would be strictly worse than the tool call it already has.

The CLI is a client here, not an agent. It FETCHES a dispatch prompt over HTTP and hands the text to the user's own coding agent, which it merely launches (BYOK).

This is the epic's target architecture: two thin adapters over ONE set of services, unified by the response SCHEMA rather than by transport. It also makes 11.6 reachable for these operations — MCP payloads cannot derive from a v1 schema that does not exist.

The audit — 19 client operations, three verdicts

COVERED by the shipped surface (9). whoamigetMe · listProjectslistProjects · listReadygetProjectReadySet · getWorkItemgetWorkItem · transitionStatustransitionWorkItem · listSprintslistProjectSprints · searchWorkItemslistProjectWorkItems · nextReady → derivable client-side off the ready set, which already returns rows in the dispatch rank · listToolNames, whose replacement is GET /api/v1/me.

NOT COVERED — no endpoint exists (10). These are this story's deliverable:

CLI methodMCP tool it mirrorsservice behind it (verified)commands that break without it
dispatchPromptdispatch_promptdispatchPromptService.getDispatchPromptmotir run · next · auto · batch
markIntegratedmark_integratedworkItemsService.markIntegratedmotir dispatch · auto
completeSessioncomplete_sessionworkItemsService.completeSessionmotir dispatch
expandItemexpand_itemaiPlanEditsService.submitExpandmotir auto --include-planning
getPlanStatusget_plan_statusaiPlanEditsService.getOutcomemotir plan
getPlanget_planplansService.getPlanmotir plan
openPlanSessionopen_plan_sessionplanChangeSessionsService.getOrCreateForScopemotir plan
appendPlanTurnappend_plan_turnplanChangeSessionsService.appendTurnmotir plan
submitPlanSessionsubmit_plan_sessionplanChangeSessionsService.submitmotir plan
getWorkItemActivityget_work_item_activityactivityService.listAll / listHistory / commentsService.listCommentsmotir show --activity

"Mirrors" is the operative word: each MCP tool proves the service path exists and shows the argument shape. Each one KEEPS working exactly as it does today.

COVERED BUT SHORT — three fields the shipped v1 schemas omit that a renderer actually reads. Verified: in every case the DATA already exists and v1 chose to narrow it away, so each is a widening rather than a new read:

  1. Per-CHILD dependency edges on the detail read. workItemDetailSchema.children is workItemRefSchema[] with no dependencies block — so assignChildWaves (packages/cli/src/render.ts:570), the build-order WAVE view 7.9.16b shipped, has no v1 source. workItemsService.getDependencyEdgesForItems is the bounded projection the MCP tool and the ready route already use.
  2. Per-ROW dependency edges on the work-item collection. workItemSummarySchema carries none, so renderSprintItems' BLOCKED BY / BLOCKS columns (7.9.16) degrade away.
  3. The blocked ANCESTOR's title. The DTO carries a full WorkItemSummaryDto (lib/dto/workItems.ts:393); lib/api/v1/workItems/schema.ts:406 narrows it to detail.readiness.blockedByAncestor?.identifier ?? null. renderReadinessLine prints blocked by ancestor <key> — <title>, so the title is dropped at the schema, not missing from the data.

The precedent that these are additive, not edge-behaviour: readyItemSchema ALREADY carries the edge block — app/api/v1/projects/[projectKey]/ready/route.ts calls getDependencyEdgesForItems as a bounded page-level projection, the exact shape ADR Amendment 3 Q4 permits.

⚠️ SCOPES ARE ALREADY DECIDED — mirror the shipped map, do not re-litigate it (corrected 2026-08-05)

An earlier draft of this card called scope mapping "a real question" and suggested the credit-spending operations were "closer to integration". That was wrong, and the correction is rung-2: lib/mcp/scopes.ts carries a TOTAL Record<McpToolName, TokenScope> map with a reasoned comment per entry, and it already considered exactly that argument and rejected it:

operationshipped scopethe map's own reasoning
dispatch_promptread"only READS the item and assembles text — it never claims it or flips its status"
get_plan · get_plan_status · open_plan_session · get_work_item_activityreadpure reads; a proposal is not a work item
expand_item · append_plan_turn · submit_plan_sessionwork_items:write"the narrowest shipped scope that admits a plan-mutating, billable submit"
mark_integrated · complete_sessionintegrationthe scope's own definition names them

So v1 MIRRORS this map. One capability model, two transports — a token granting read must mean the same thing whichever door it arrives at, and inventing a second mapping would make a scope's meaning depend on transport, which is precisely the drift this epic exists to end. If mirroring turns out to be wrong for one operation, that is an amendment to the SHARED map with its reasoning updated, not a v1-only divergence.

What to build

Every endpoint is a thin adapter over the service named in the table above. The paths below are CANDIDATES; this story's decision subtask pins them:

  • Dispatch — the canonical server-generated prompt, a pure READ that never claims the item (7.9.0a), carrying targetRepo, workflowMode, sessionBranch and the prose-vs-graph advisories block (MOTIR-2079). Candidate: GET /api/v1/work-items/{key}/dispatch-prompt?sessionBranch=.
  • Session close-out — record one item as integrated on a session branch, and the bulk close of every item on a merged branch with per-item outcomes. Candidates: POST /api/v1/work-items/{key}/integration, POST /api/v1/sessions/{sessionBranch}/complete.
  • Expansion — submit an AI expansion and return { jobId, planId } the instant motir-ai accepts it. Candidate: POST /api/v1/work-items/{key}/expansions.
  • The planning conversation — open/resume a thread addressed by SCOPE (never by session id, so a second conversation about one anchor set cannot be forked), append one turn, submit the accumulated intent.
  • The plan read — a plan's status (including whether a still-generating job is alive) and a plan WITH its proposals.
  • The activity read — the changes and all views of MOTIR-1999, whose all cursor is an OPAQUE composite over both sources.
  • The three field projections above — additive under ADR §8, so no client breaks.

Completeness

Every collection is paged in the v1 cursor envelope, including the activity stream's composite cursor. Empty, not-found, forbidden, rate-limited and validation-failure responses are part of the deliverable. Two operations submit JOBS rather than writing rows — expansion and plan submission — so the contract each publishes is "accepted", never "finished": the response is the job handle, the plan it produces is PROPOSALS, and approval in Motir remains the only path from a proposal to a work item.

Type sweep — what this story does NOT need, deliberately

design — no rendered surface; the API reference renders itself from the emitted spec (11.4) and gains these operations for free. doc — for the same reason: the reference is generated, so there is no hand-written API page to update. manual/human — no account, secret, DNS or dashboard step. deploy — no new infrastructure. copy/translate — no user-visible string; an API error message is not localized product copy. legal — no new artifact. research/spike — the decision subtask carries the grounding. So: one decision, six code, two test.

No acceptance video. No user-observable surface — exempt under the non-UI carve-out, exactly as 11.111.3 did. The conformance suite is this story's E2E; the client is curl.

Scope BOUNDARY

Ends at the work-loop operations the CLI and third parties consume, plus the activity read and the three field projections. ONE repo — motir-core.

It does NOT touch the MCP surface, in any direction. No tool is re-pointed, re-shaped, renamed, deprecated or removed; app/api/mcp and lib/mcp/ are read for reference and left as they are. Agents continue on MCP and are not a consumer of anything this story builds. (Making the two DERIVE from one schema is 11.6's job; this story is what gives 11.6 a schema for these operations to derive from.)

It does NOT expose every remaining MCP tool: claim_next_ready, get_project_state, validate_work_item and validate_sprint have no client asking for them and stay MCP-only until one does. It does NOT change what dispatch_prompt ASSEMBLES, what an expansion job DOES, or how a plan session accumulates — every behaviour here already ships and this story publishes it. It does NOT migrate the CLI (11.5).

On services: beyond the three additive response projections, it changes no service, repository or migration. If an endpoint wants DATA a service does not expose, that is a card in the owning feature's epic — not a service change smuggled in at the edge.

Acceptance criteria

  • Every operation in the NOT-COVERED table has a /api/v1 endpoint, declared in the operation registry, appearing in the emitted OpenAPI document, and gated on the same scope its MCP counterpart carries in lib/mcp/scopes.ts — asserted per operation, against that map rather than a copy of it.
  • Every corresponding MCP tool still exists and behaves identically — asserted by the shipped MCP suites passing untouched, so "agents keep MCP" is a property a test holds.
  • The three field projections land on workItemDetailSchema (per-child dependencies), workItemSummarySchema (per-row dependencies) and readinessSchema (the blocked ancestor's title alongside its key), each produced by a BOUNDED projection over the page already read — never a per-row query.
  • packages/cli/src/render.ts's assignChildWaves, renderSprintItems and renderReadinessLine can each be fed from v1 responses alone — proven by a test driving the real renderer with a real v1 payload.
  • The activity endpoint serves all three views and round-trips its OPAQUE composite cursor; a cursor from another collection is refused, never silently reset.
  • Expansion and plan-submit return the job handle without waiting for the planner, and their response schemas make it unmistakable that a plan holds PROPOSALS, not work items.
  • Every response body is produced by a declared zod schema in this story's own resource module, per ADR Amendment 2 as totalized by Amendment 5.
  • A token lacking an operation's mapped scope is refused (403) on every endpoint here, asserted per operation.
  • Cross-tenant reads and writes are refused with the product's 404-not-403 behaviour, asserted for every endpoint taking a key.
  • Every domain error these endpoints can raise has a deliberate row in the v1 status map, proven by a test that drives it.
  • No route calls Prisma or opens a transaction, and none imports from lib/mcp/tools/ — each is one service call.
  • The per-file coverage floor (≥90%) holds on every new file.

Context refs

  • lib/services/dispatchPromptService.ts · workItemsService.markIntegrated / .completeSession / .getDependencyEdgesForItems (line ~3885) · aiPlanEditsService.submitExpand / .getOutcome · plansService.getPlan · planChangeSessionsService.getOrCreateForScope / .appendTurn / .submit · activityService.listAll / .listHistory · commentsService.listComments — every service this story adapts, verified present.
  • lib/mcp/tools/dispatchPrompt.ts · markIntegrated.ts · completeSession.ts · expandItem.ts · planSession.ts · getPlan.ts · getWorkItemActivity.ts — the argument shapes and semantics to mirror. Read for reference, left in place — v1 routes must not import from here.
  • lib/mcp/scopes.ts — the TOTAL tool→scope map v1 mirrors, with its per-entry reasoning.
  • lib/api/v1/route.tswithV1Route(options, handler): authenticate → rate-limit → handle, with options.scope the gate.
  • lib/api/v1/workItems/schema.tsworkItemDetailSchema, workItemSummarySchema, readinessSchema (line ~240) and the mapper at line ~406 that narrows the ancestor away.
  • lib/dto/workItems.ts:393blockedByAncestor: WorkItemSummaryDto | null, the full object the schema currently drops.
  • app/api/v1/projects/[projectKey]/ready/route.ts — the shipped precedent for a bounded page-level edge projection.
  • lib/api/v1/openapi/registry.ts — where a new resource module is registered; tests/api/v1/openapi-operations-coverage.test.ts fails on any route method with no declaration.
  • packages/cli/src/render.ts — where the three field gaps bite: line 570, 372, 739.
  • Consumer: 11.5. Producers it composes: 11.2, 11.4. Derives from it later: 11.6. Parent epic: the public REST API.