PinAppAI

← All docs

Iteration loop architecture

How PinAppAI v2 organizes review rounds: atomic iterations with frozen manifests, an event-sourced state machine, drift detection, and a dashboard built around what's pending vs. what's been resolved.


PinAppAI’s review backbone is iteration-first as of v2: a project’s change requests don’t just have an admin status, they belong to iterations — discrete rounds of review with a frozen manifest, a reviewer roster snapshot, and an event log that traces every state change. This page documents the model so you can read a project’s history accurately, drive iterations from MCP, or build integrations on top of the new endpoints.

If you’ve used the legacy status-first dashboard (Open / Processing / Done / Wontfix chips), the new model maps onto it cleanly and runs alongside it during the migration window.

The loop

[setup-project]                     ← install the widget + reviewers


reviewers leave change requests on the live site


admin triages → schedules → opens iteration N         ← manifest frozen here
   │                                                     (CR ids + reviewer roster)

AI agent applies the bundle                            ← /pinappai:apply-decisions
   │                                                     state moves scheduled → applied

reviewers decide on each CR                            ← state moves applied → in_review


admin acknowledges per CR

   ├── accept → closed_accepted (terminal)

   └── reopen → scheduled (loop back; iterations_traversed += 1)


              [iteration N+1] picks it up

The loop terminates per CR when the admin acknowledges with accept (or sets wontfix directly, which is also terminal). Iterations themselves don’t “close” — they’re just applied; whether their CRs continue to circulate is a per-CR question.

What an iteration captures

Each iteration row holds:

  • seq_no — 1, 2, 3, … per project
  • opened_at / applied_at — wall-clock timestamps; applied_at IS NULL means open
  • manifest_cr_ids_json — the frozen list of CR ids included at open time
  • manifest_reviewer_ids_json — the reviewer roster as of open time (snapshot)
  • bundle_summary — short text added at apply time, surfaced in History
  • sourceadmin, mcp, or backfill (synthesized from legacy data)

The manifest freezes at open: adding a reviewer or another CR after that moment doesn’t retroactively change iteration N’s roster — they show up in iteration N+1 instead. This is what makes “the 14 rejected ones from this round” answerable as a stable query.

State machine

Each change request carries a cached state column whose value is the fold of its event log:

   triage → scheduled → applied → in_review →
                ↑                              ├── accept → closed_accepted (terminal)
                │                              └── reopen → scheduled (loops)
   wontfix ←───┘     (terminal — no re-entry from in_review)

The cache exists because folding events on every dashboard read would be too slow on D1 above a few hundred CRs per project. A nightly drift-detection cron walks every CR, re-derives state from events, and logs any mismatch to cr_state_drift_log — this is what keeps the cache honest and surfaces missed dual-write hooks before they snowball.

Coverage — the headline metric

For an iteration with N CRs in its manifest:

  • items_decided = how many of the N have at least one reviewer_decided event
  • items_undecided = N − items_decided
  • disagreement_count = how many of the N have conflicting decisions across reviewers
  • reviewers[] = per-reviewer count of decided items

The “headline coverage” is items_decided / manifest_size — that’s what the admin dashboard’s coverage bar visualizes and what pinappai_get_iteration_coverage returns to your AI agent. Per-reviewer breakdown is diagnostic (“yigit 30/50, maya 20/50, tom 0/50, 80% items touched”).

Driving iterations from MCP

@pinappai/[email protected] adds 5 tools that map 1:1 to the atomic primitives:

  • pinappai_open_iteration — opens iteration N+1 with a frozen manifest
  • pinappai_mark_iteration_applied — moves manifest CRs to applied
  • pinappai_acknowledge_change_request — per-CR accept/reopen
  • pinappai_get_iteration_coverage — read-only coverage snapshot
  • pinappai_list_iterations — paginated history

A typical AI-driven loop looks like:

# Inside Claude Code or any MCP-aware agent:
/pinappai:apply-decisions   # uses pinappai_open_iteration internally,
                            #   walks the manifest, edits source,
                            #   then calls pinappai_mark_iteration_applied

The slash command writes the .pinappai/last-applied.json marker as before — that file is now a 1-line cache pointing at the server-side iteration id, not the source of truth. Older @pinappai/mcp versions (≤ 0.4.0) keep working against the legacy compatibility shim through a 6-week deprecation window.

When to pick which surface

SurfaceBest for
app.pinappai.com/dashboard-v2Manual review, coverage glance, ad-hoc per-CR acknowledge
@pinappai/mcp from Claude Code / CursorAI-driven apply + acknowledge, large iterations, programmatic workflows
Legacy status-first dashboardExisting muscle memory; still fully supported through the migration window

The new dashboard is opt-in via the topbar link until your workspace’s dashboard_version flag is flipped to 'new' — at which point the project’s default landing page becomes iteration-first. Both layouts read the same underlying data, so flipping back and forth is non-destructive.

Hand-off into the build

Implementation lives in:

  • API: apps/api/src/lib/iterations.ts (atomic primitives), apps/api/src/routes/admin-iterations.ts (session auth), apps/api/src/routes/me-iterations.ts (API key auth)
  • Schema: migrations 0030–0034 (iterations, change_requests, change_request_events, workspaces.dashboard_version, cr_state_drift_log)
  • Drift cron: apps/api/src/lib/cr-state-drift.ts runs daily at 03:00 UTC
  • MCP tools: apps/mcp/src/tools/{open-iteration,mark-iteration-applied,acknowledge-change-request,get-iteration-coverage,list-iterations}.ts

For the design rationale and trade-off discussion that drove this shape, see the design plan: docs/superpowers/plans/2026-05-07-iteration-loop-v2.md.