> ## Documentation Index
> Fetch the complete documentation index at: https://docs.placet.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Iterations & Versioning

> Build revision workflows where agents iterate on content based on human feedback.

## Overview

Placet's **iteration system** turns one-shot reviews into multi-round revision workflows. An agent sends content, a human provides feedback, the agent revises, and the human reviews again — as many rounds as needed.

All iterations of the same task are linked into an **iteration chain**, enabling diff views, feedback history, and progress tracking across revisions.

```mermaid theme={null}
sequenceDiagram
    participant Agent as 🤖 Agent
    participant API as Placet API
    participant Human as 👤 Human

    Agent->>API: Send draft (iteration #1)
    API->>Human: Review request
    Human->>API: Respond with feedback
    API-->>Agent: status: completed + feedback
    Note over Agent: Agent decides to revise
    Agent->>API: Send revision (iterationOf: msg1)
    Note over API: Linked as iteration #2
    API->>Human: Re-review with diff
    Human->>API: Approve ✓
    API-->>Agent: status: completed
```

## When to Use Iterations

| Scenario               | Without iterations                          | With iterations                                |
| ---------------------- | ------------------------------------------- | ---------------------------------------------- |
| **Content generation** | Agent sends once, human approves or rejects | Human requests specific changes, agent revises |
| **Report review**      | Binary approve/reject, agent starts over    | Feedback loop until quality met                |
| **Code review**        | Human re-explains issues each time          | Previous feedback visible in context           |
| **Image generation**   | No connection between attempts              | Side-by-side comparison with previous version  |

## How It Works

### Data Model

Iterations are tracked with two fields on the `Message` model — no new tables:

| Field              | Type      | Description                                                                    |
| ------------------ | --------- | ------------------------------------------------------------------------------ |
| `iterationGroupId` | `string?` | Points to the root message ID of the chain. `null` for standalone messages.    |
| `iteration`        | `int?`    | Counter within the group. `1` = root, `2`+ = revisions. `null` for standalone. |

A unique constraint on `(iterationGroupId, iteration)` prevents race conditions.

```mermaid theme={null}
flowchart LR
    M1["Message #1<br/>iteration: 1<br/>iterationGroupId: msg1<br/>status: completed"]
    M2["Message #2<br/>iteration: 2<br/>iterationGroupId: msg1<br/>status: completed"]
    M3["Message #3<br/>iteration: 3<br/>iterationGroupId: msg1<br/>status: completed ✓"]

    M1 -->|feedback| M2 -->|feedback| M3

    style M1 fill:#6b7280,color:#fff
    style M2 fill:#6b7280,color:#fff
    style M3 fill:#22c55e,color:#fff
```

### Review Statuses

| Status      | Meaning                 | Wait endpoint behavior |
| ----------- | ----------------------- | ---------------------- |
| `pending`   | Awaiting human response | Continues polling      |
| `completed` | Human responded         | Returns immediately    |
| `expired`   | Timed out (default 24h) | Returns immediately    |

<Note>
  Every review response is `completed`. The agent interprets the response content (e.g. selected
  option, feedback text) and decides whether to send a new iteration. Placet is a transport layer —
  it does not encode business logic about what constitutes "approval" vs "rejection".
</Note>

## Sending Iterations

### Step 1: Send the initial message

Send a message with a review as usual. No special parameters needed — the first message becomes the chain root automatically when an iteration references it.

```json theme={null}
POST /api/v1/messages
{
  "channelId": "agent-123",
  "text": "Here is the Q1 report draft.\n\n- Revenue: $1.2M\n- Growth: 15%",
  "status": "warning",
  "metadata": { "runId": "run-001", "model": "gpt-4" },
  "review": {
    "type": "approval",
    "payload": {
      "options": [
        { "id": "approve", "label": "Approve", "style": "primary" },
        { "id": "reject", "label": "Reject", "style": "danger" }
      ]
    }
  }
}
```

### Step 2: Wait for the response

Use the wait endpoint to poll for the human's decision:

```json theme={null}
GET /api/v1/reviews/{messageId}/wait?channel=agent-123&timeout=120000
```

The response includes the review status and human feedback:

```json theme={null}
{
  "status": "completed",
  "message": {
    "id": "msg-1",
    "review": {
      "status": "completed",
      "feedback": "Please add Q2 projections and fix the growth calculation.",
      "response": { "selectedOption": "reject" }
    }
  }
}
```

### Step 3: Send a revised iteration

Reference the previous message via `iterationOf` to create an iteration chain:

```json theme={null}
POST /api/v1/messages
{
  "channelId": "agent-123",
  "text": "Revised Q1 report with Q2 projections.\n\n- Revenue: $1.2M\n- Growth: 18% (corrected)\n- Q2 Projection: $1.4M",
  "status": "warning",
  "metadata": { "runId": "run-002", "model": "gpt-4" },
  "iterationOf": "msg-1",
  "review": {
    "type": "approval",
    "payload": {
      "options": [
        { "id": "approve", "label": "Approve", "style": "primary" },
        { "id": "reject", "label": "Reject", "style": "danger" }
      ]
    }
  }
}
```

The response includes the iteration metadata:

```json theme={null}
{
  "id": "msg-2",
  "iterationGroupId": "msg-1",
  "iteration": 2,
  "text": "Revised Q1 report with Q2 projections...",
  ...
}
```

### Step 4: Repeat until approved

Continue the loop — check the wait endpoint, and based on the response content, decide whether to send another iteration:

```mermaid theme={null}
flowchart TD
    Send["Send message<br/>(with iterationOf if revision)"]
    Wait["Wait for review response"]
    Check{"Agent interprets<br/>response"}
    Revise["Revise content using<br/>feedback from response"]
    Done["✅ Workflow complete"]

    Send --> Wait --> Check
    Check -->|needs revision| Revise
    Check -->|approved| Done
    Revise --> Send
    Check -->|expired| Done

    style Done fill:#22c55e,color:#fff
    style Revise fill:#f59e0b,color:#000
```

## Interpreting Responses (Agent Side)

Placet delivers the human's response as-is. The agent (or workflow) decides what constitutes "approval" vs "rejection":

* **Approval reviews** — check `response.selectedOption` (e.g. `"approve"` vs `"reject"`)
* **Form reviews** — inspect submitted field values
* **Text input** — read the text response and optionally the `feedback` field
* **Selection** — check `response.selectedItems`

The `feedback` field on the review response contains optional human-provided text explaining their decision. Use it to guide the next iteration.

<Tip>
  Define your review options to make agent-side interpretation simple. For example, use
  `{ id: "approve", label: "Approve" }` and `{ id: "revise", label: "Request Changes" }` —
  then check `response.selectedOption === "revise"` in your agent loop.
</Tip>

## Retrieving Iteration Chains

### Agent API

```bash theme={null}
GET /api/v1/messages/iterations/{messageId}?channel={channelId}
```

### Response

```json theme={null}
{
  "groupId": "msg-1",
  "iterations": [
    {
      "id": "msg-1",
      "iteration": 1,
      "text": "Draft report v1...",
      "review": {
        "status": "completed",
        "feedback": "Add Q2 data",
        "response": { "selectedOption": "reject" }
      }
    },
    {
      "id": "msg-2",
      "iteration": 2,
      "text": "Revised report with Q2...",
      "review": { "status": "completed", "response": { "selectedOption": "approve" } }
    }
  ]
}
```

You can pass **any message ID** from the chain — the API returns the complete chain sorted by iteration number.

## Validation Rules

The `iterationOf` parameter is validated by the backend:

| Rule                                           | Error                                                  |
| ---------------------------------------------- | ------------------------------------------------------ |
| Target message must exist                      | `400 Bad Request`                                      |
| Target must be in the same channel             | `400 Bad Request`                                      |
| Target review must be `completed` or `expired` | `400 Bad Request` — cannot iterate on a pending review |

<Warning>
  Messages in an iteration chain cannot be deleted while other messages reference the same group.
  This protects the integrity of the chain.
</Warning>

## Diff & Comparison Views

When iterations exist, the web app automatically shows what changed between versions:

* **Text diff** — inline additions (green) and deletions (red) between iterations
* **Image comparison** — side-by-side view of previous and current image attachments
* **Iteration breadcrumbs** — clickable `① → ② → ③` navigation with status indicators
* **Previous feedback** — the feedback from the last iteration is displayed for context

These views appear in the chat detail view, file preview modal, and the inbox detail.

## Inbox Integration

The inbox is iteration-aware:

* **Re-Review badge** — messages that are iteration #2+ show a "Re-Review" tag
* **Iteration breadcrumbs** — navigate between iterations directly from the inbox detail
* **Diff toggle** — compare the current iteration with the previous one
* **Pending reviews bar** — floating bar at the top of chat showing open reviews, with iteration chains grouped

## Webhook Events

When a human responds to a review, a webhook is delivered with:

```json theme={null}
{
  "event": "review:responded",
  "messageId": "msg-1",
  "channelId": "agent-123",
  "iteration": 1,
  "iterationGroupId": "msg-1",
  "feedback": "Please add Q2 projections.",
  "response": { "selectedOption": "reject" },
  "review": { ... }
}
```

The agent inspects the `response` and `feedback` fields to decide whether to iterate.

## MCP Server Tools

The MCP server exposes iteration support through enhanced tools:

| Tool                  | Iteration support                                |
| --------------------- | ------------------------------------------------ |
| `send_message`        | `iterationOf` parameter to chain messages        |
| `send_review_message` | `iterationOf` parameter to chain review messages |
| `wait_for_review`     | Returns `completed` with response and feedback   |
| `get_iteration_chain` | New tool — fetch all iterations for a message    |

## Agent Loop Pattern

A typical agent implementation handles iterations with a simple loop:

<CodeGroup>
  ```python Python theme={null}
  import requests, time

  def run_agent(channel, api_key):
  session = requests.Session()
  session.headers["x-api-key"] = api_key
  base = "http://localhost:3001"

      iteration_of = None
      content = generate_initial_content()

      while True:
          body = {
              "channelId": channel,
              "text": content,
              "metadata": {"generatedAt": time.time()},
              "review": {
                  "type": "approval",
                  "payload": {"options": [
                      {"id": "approve", "label": "Approve"},
                      {"id": "reject", "label": "Reject"},
                  ]},
              },
          }
          if iteration_of:
              body["iterationOf"] = iteration_of

          msg = session.post(f"{base}/api/v1/messages", json=body).json()

          result = session.get(
              f"{base}/api/v1/reviews/{msg['id']}/wait",
              params={"channel": channel, "timeout": "120000"},
          ).json()

          # Agent interprets the response — Placet just delivers it
          response = result["message"]["review"].get("response", {})
          if response.get("selectedOption") == "reject":
              feedback = result["message"]["review"].get("feedback", "")
              content = revise_content(content, feedback)
              iteration_of = msg["id"]
              continue  # next iteration

          break  # approved, expired, or timed out

  ```

  ```typescript TypeScript theme={null}
  async function runAgent(channel: string, apiKey: string) {
    const base = "http://localhost:3001";
    const headers = { "x-api-key": apiKey, "Content-Type": "application/json" };

    let iterationOf: string | undefined;
    let content = generateInitialContent();

    while (true) {
      const body: Record<string, unknown> = {
        channelId: channel,
        text: content,
        metadata: { generatedAt: Date.now() },
        review: {
          type: "approval",
          payload: { options: [
            { id: "approve", label: "Approve" },
            { id: "reject", label: "Reject" },
          ]},
        },
      };
      if (iterationOf) body.iterationOf = iterationOf;

      const msg = await fetch(`${base}/api/v1/messages`, {
        method: "POST", headers, body: JSON.stringify(body),
      }).then(r => r.json());

      const result = await fetch(
        `${base}/api/v1/reviews/${msg.id}/wait?channel=${channel}&timeout=120000`,
        { headers },
      ).then(r => r.json());

      // Agent interprets the response — Placet just delivers it
      const response = result.message?.review?.response;
      if (response?.selectedOption === "reject") {
        const feedback = result.message?.review?.feedback ?? "";
        content = reviseContent(content, feedback);
        iterationOf = msg.id;
        continue; // next iteration
      }

      break; // approved, expired, or timed out
    }
  }
  ```
</CodeGroup>

## Using Metadata for Agent Context

The `metadata` field on messages is a free-form JSON object that the agent controls. Placet stores it unchanged and returns it in all API responses. Use it to track agent-specific context across iterations:

```json theme={null}
{
  "metadata": {
    "runId": "run-abc-123",
    "model": "gpt-4",
    "promptVersion": "v2.1",
    "tokensUsed": 1847,
    "previousFeedback": ["Add Q2 data", "Fix growth %"]
  }
}
```

<Tip>
  Store your agent's run context in `metadata` so you can reconstruct state across iterations.
  Placet never reads or modifies this field.
</Tip>

## n8n / Automation Workflow Pattern

For workflow automation tools like n8n, implement the iteration loop using a **Switch node** after the wait step:

```mermaid theme={null}
flowchart LR
    Send["HTTP Request<br/>POST /messages"]
    Wait["HTTP Request<br/>GET /reviews/.../wait"]
    Switch{"Switch on<br/>response content"}
    Revise["AI Node<br/>Revise content"]
    Done["✅ Done"]

    Send --> Wait --> Switch
    Switch -->|rejected| Revise --> Send
    Switch -->|approved| Done
    Switch -->|expired| Done

    style Done fill:#22c55e,color:#fff
    style Revise fill:#f59e0b,color:#000
```

The key is the loop back from the Switch node to the Send node, passing the previous message ID as `iterationOf` and incorporating the feedback from the response. The Switch node inspects the response content (e.g. `selectedOption`) to determine the next step.
