Skip to content
Sneak peek — you found hosted ggui early · official launch soon

Showcase: a real turn

read as .md

Two turns, one thread, two rendered surfaces. Read the story; open any On the wire panel to see exactly what crossed the wire at that beat. Nothing on this page is typed by hand: each panel is read out of the committed recording at build time, and a panel that names a frame the recording does not contain fails the build rather than quietly showing you prose.

A visitor — anonymous, no account — types “What does Sam's week look like for a haircut?”. The rig behind this recording invoked the agent the way that visitor would: over the host’s guest wire, with no sign-in and no bearer token. One POST goes out, and the first frame back is the session, which is where that shows up on the wire: authMode: "anonymous", a caller id standing in for the guest hash the real exchange carried (placeholdered here like every other id), and the threadId that names the durable thread every later turn replays against.

On the wire 1 frame · turn 1
event: session
{
  "sessionId": "session_1",
  "userId": "user_1",
  "authMode": "anonymous",
  "threadId": "thread_1"
}

Ids are stable placeholders (thread_1, call_2); credentials were removed at capture, never recorded.

2 · Handshake — the contract, negotiated

Section titled “2 · Handshake — the contract, negotiated”

Before any UI exists, the agent calls ggui_handshake with an intent and a draft contract. The draft is the interesting half: the agent describes, in schema, the surface it wants. The server answers with a proposed contract, an action (here reuse), and an origincache (a similar UI was built before and is reused), agent (the draft was already clean), or synth (the server repaired it). Open the panel to read the server’s rationale: which blueprint it matched, at what confidence, and every coverage gap between the draft and the cached surface.

The contract is where the protocol’s placement rule lives: actionSpec = discrete events that drive the agent’s next turn, contextSpec = observable state the agent reads when it next works, and there is no third category — see the placement test and Envelopes.

Watch what the two frames in the panel do with that. This ask wanted an action: the draft declares actionSpec.pickSlot alongside its props. The contract the server proposed — reused from a cached blueprint — carries no such thing: slots=∅; actions=selectSlot(date,dayName,time); streams=∅; props=days:array!,personName:string!,service:string!,weekLabel:string!. The differences come back as 2 COVERAGE_GAP findings at warnactionSpec.pickSlot, propsSpec.properties.apptLabel — never a veto, with the server's own counsel attached: "Default to ACCEPT (reuse-and-refine) — override only if the user must directly see or act on this surface". The agent accepted, so the grid this recording mounted is a read-only card. Re-aiming the contract instead is a call the protocol gives it — beat 6 is where that lever appears.

On the wire 3 frames · turn 1
event: message
{
  "type": "tool.start",
  "seq": 11,
  "turnId": "turn_1",
  "toolCallId": "call_1",
  "name": "mcp__ggui__ggui_handshake",
  "index": 1,
  "messageId": "msg_011Ce6CrMJCuVVxgt14wrhX9",
  "providerExecuted": false
}
event: message
{
  "type": "tool.args.assembled",
  "seq": 195,
  "toolCallId": "call_1",
  "input": {
    "intent": "Weekly availability grid for booking a haircut slot",
    "blueprintDraft": {
      "contract": {
        "propsSpec": {
          "description": "A week-at-a-glance grid showing a person's open and busy times, highlighting good slots for a short appointment like a haircut.",
          "properties": {
            "personName": {
              "schema": {
                "type": "string"
              },
              "required": true,
              "example": "Sam"
            },
            "weekLabel": {
              "schema": {
                "type": "string"
              },
              "required": true,
              "example": "Aug 16 - Aug 22, 2026"
            },
            "apptLabel": {
              "schema": {
                "type": "string"
              },
              "required": true,
              "example": "Haircut (30 min)"
            },
            "days": {
              "required": true,
              "schema": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "day": {
                      "type": "string"
                    },
                    "date": {
                      "type": "string"
                    },
                    "slots": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "time": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "open",
                              "busy",
                              "best"
                            ]
                          },
                          "note": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "time",
                          "status"
                        ]
                      }
                    }
                  },
                  "required": [
                    "day",
                    "date",
                    "slots"
                  ]
                }
              },
              "example": [
                {
                  "day": "Mon",
                  "date": "Aug 17",
                  "slots": [
                    {
                      "time": "9:00 AM",
                      "status": "busy",
                      "note": "Team sync"
                    },
                    {
                      "time": "1:00 PM",
                      "status": "open"
                    }
                  ]
                }
              ]
            }
          }
        },
        "actionSpec": {
          "pickSlot": {
            "label": "Pick this slot",
            "description": "Visitor taps a slot to see it called out as their pick",
            "schema": {
              "type": "object",
              "properties": {
                "day": {
                  "type": "string"
                },
                "time": {
                  "type": "string"
                }
              },
              "required": [
                "day",
                "time"
              ]
            }
          }
        }
      }
    }
  }
}
event: message
{
  "type": "tool.done",
  "seq": 197,
  "turnId": "turn_1",
  "toolCallId": "call_1",
  "content": [
    {
      "type": "text",
      "text": "{\"handshakeId\":\"handshake_1\",\"action\":\"reuse\",\"suggestion\":{\"origin\":\"cache\",\"rationale\":\"match-semantic: judge matched bp_id_4 (cosine=0.47, confidence=0.85) — Both are weekly haircut availability grids with the same task intent and shape; the candidate's selectSlot action is a semantic match to pickSlot, and shared props (days, personName, weekLabel) align well despite minor field differences. [coverage gap — the request declares surfaces this cached UI lacks (actions: pickSlot; props: apptLabel); agent override is the safety valve]\",\"blueprintMeta\":{\"blueprintId\":\"blueprint_1\",\"contractHash\":\"c67119865ad0b290\",\"codeHash\":\"6967d0c2c0defe990cffd3ca7b43009d7cff55c6004b39559e67d23a191919b3\",\"source\":{\"kind\":\"llm\",\"generator\":\"ui-gen-default-haiku-4-5\",\"model\":\"claude-haiku-4-5-20251001\"},\"variance\":{},\"selectedReason\":\"match-semantic: judge matched bp_id_4 (cosine=0.47, confidence=0.85) — Both are weekly haircut availability grids with the same task intent and shape; the candidate's selectSlot action is a semantic match to pickSlot, and shared props (days, personName, weekLabel) align well despite minor field differences. [coverage gap — the request declares surfaces this cached UI lacks (actions: pickSlot; props: apptLabel); agent override is the safety valve]\"},\"proposedContractSummary\":\"slots=∅; actions=selectSlot(date,dayName,time); streams=∅; props=days:array!,personName:string!,service:string!,weekLabel:string!\",\"validationFindings\":[{\"code\":\"COVERAGE_GAP\",\"severity\":\"warn\",\"path\":\"actionSpec.pickSlot\",\"message\":\"the proposed cached UI does not declare actions 'pickSlot' that your draft requests. Default to ACCEPT (reuse-and-refine) — override only if the user must directly see or act on this surface, since the cached UI cannot show it.\"},{\"code\":\"COVERAGE_GAP\",\"severity\":\"warn\",\"path\":\"propsSpec.properties.apptLabel\",\"message\":\"the proposed cached UI does not declare props 'apptLabel' (required in your draft) that your draft requests. Default to ACCEPT (reuse-and-refine) — override only if the user must directly see or act on this surface, since the cached UI cannot show it.\"}]},\"nextStep\":{\"tool\":\"ggui_render\",\"example\":\"ggui_render({\\\"handshakeId\\\":\\\"handshake_1\\\",\\\"props\\\":{\\\"personName\\\":\\\"Sam\\\",\\\"service\\\":\\\"Haircut (30 min)\\\",\\\"weekLabel\\\":\\\"Aug 17–23, 2026\\\",\\\"days\\\":[{\\\"date\\\":\\\"\\\",\\\"dayName\\\":\\\"\\\",\\\"slots\\\":[{\\\"time\\\":\\\"\\\",\\\"status\\\":\\\"open\\\"}]}]}})\"}}"
    }
  ],
  "outcome": "ok",
  "isError": false,
  "messageId": "toolu_01ECLyjB6TQ7Yqng6XEsiM8h:result",
  "structuredContent": {
    "handshakeId": "handshake_1",
    "action": "reuse",
    "suggestion": {
      "origin": "cache",
      "rationale": "match-semantic: judge matched bp_id_4 (cosine=0.47, confidence=0.85) — Both are weekly haircut availability grids with the same task intent and shape; the candidate's selectSlot action is a semantic match to pickSlot, and shared props (days, personName, weekLabel) align well despite minor field differences. [coverage gap — the request declares surfaces this cached UI lacks (actions: pickSlot; props: apptLabel); agent override is the safety valve]",
      "blueprintMeta": {
        "blueprintId": "blueprint_1",
        "contractHash": "c67119865ad0b290",
        "codeHash": "6967d0c2c0defe990cffd3ca7b43009d7cff55c6004b39559e67d23a191919b3",
        "source": {
          "kind": "llm",
          "generator": "ui-gen-default-haiku-4-5",
          "model": "claude-haiku-4-5-20251001"
        },
        "variance": {},
        "selectedReason": "match-semantic: judge matched bp_id_4 (cosine=0.47, confidence=0.85) — Both are weekly haircut availability grids with the same task intent and shape; the candidate's selectSlot action is a semantic match to pickSlot, and shared props (days, personName, weekLabel) align well despite minor field differences. [coverage gap — the request declares surfaces this cached UI lacks (actions: pickSlot; props: apptLabel); agent override is the safety valve]"
      },
      "proposedContractSummary": "slots=∅; actions=selectSlot(date,dayName,time); streams=∅; props=days:array!,personName:string!,service:string!,weekLabel:string!",
      "validationFindings": [
        {
          "code": "COVERAGE_GAP",
          "severity": "warn",
          "path": "actionSpec.pickSlot",
          "message": "the proposed cached UI does not declare actions 'pickSlot' that your draft requests. Default to ACCEPT (reuse-and-refine) — override only if the user must directly see or act on this surface, since the cached UI cannot show it."
        },
        {
          "code": "COVERAGE_GAP",
          "severity": "warn",
          "path": "propsSpec.properties.apptLabel",
          "message": "the proposed cached UI does not declare props 'apptLabel' (required in your draft) that your draft requests. Default to ACCEPT (reuse-and-refine) — override only if the user must directly see or act on this surface, since the cached UI cannot show it."
        }
      ]
    },
    "nextStep": {
      "tool": "ggui_render",
      "example": "ggui_render({\"handshakeId\":\"handshake_1\",\"props\":{\"personName\":\"Sam\",\"service\":\"Haircut (30 min)\",\"weekLabel\":\"Aug 17–23, 2026\",\"days\":[{\"date\":\"\",\"dayName\":\"\",\"slots\":[{\"time\":\"\",\"status\":\"open\"}]}]}})"
    }
  }
}

Ids are stable placeholders (thread_1, call_2); credentials were removed at capture, never recorded.

One detail the raw frames show and this page smooths over: hosts may namespace MCP tool names. In these recordings the wire name is mcp__ggui__ggui_handshake; the protocol name is ggui_handshake, and readers of the stream should match on the bare name.

3 · Render — and the contract check that refuses a bad one

Section titled “3 · Render — and the contract check that refuses a bad one”

ggui_render commits the contract and returns the view’s identity — not its material. On this host’s stream that arrives as a tool.done frame carrying the render’s JSON both as text and as the result’s structuredContent: resourceUri, the view’s durable ui:// identity; action, reporting whether the surface was created or reused; contractHash; and a cache record. What is deliberately not on the wire is the mount material — the bootstrap that boots the runtime and opens the live channel, the compiled component, the theme. A host resolves those by reading resourceUri (MCP resources/read), the read-plane-only posture: the tool result names the view, the read plane serves it — see Bootstrap handshake.

Note what the agent never writes: component code. Its call is handshakeId plus props — and, when it wants to re-aim the contract it was offered, an override — but read the assembled-arguments frame in the panel and you will not find markup, styling, or a component anywhere in it. Authoring the UI is not the agent’s job.

So where is the code? Not in the panel — and that is the point. Under the read-plane-only posture the tool result carries no ai.ggui/render slice at all: the host takes the resourceUri above to MCP resources/read and receives the shell — the bootstrap (which runtime to load, which live channel to open), the compiled component, the theme — from the read plane, cached and content-addressed. The agent's stream stays small and identity-shaped; the multi-kilobyte material never rides the conversation. It was generated server-side, from the contract; the agent neither wrote it nor saw it.

A negotiated contract is enforced, not advised. When the props an agent commits violate the contract it just agreed to, the render is refused with a typed error naming the exact offending path, and the agent repairs the call and renders again — no human in the loop. That is the check working, not the agent failing.

In this turn the check fired. The panel below carries every render call and result in wire order: the first result is a refusal, and the last is the success. The violation is worth reading in full — it names a path and the vocabulary the contract declared: days[0].slots[0].note: Undeclared field 'note' at 'days[0].slots[0]'. Declared keys: [time, status]. (expected: <declared key>, got: string). Other violations take the same shape: a field the contract never declared, a string where an object was specified, each reported by path.

On the wire 4 frames · turn 1
event: message
{
  "type": "tool.args.assembled",
  "seq": 400,
  "toolCallId": "call_2",
  "input": {
    "handshakeId": "handshake_1",
    "props": {
      "personName": "Sam",
      "service": "Haircut (30 min)",
      "weekLabel": "Aug 17–23, 2026",
      "days": [
        {
          "dayName": "Mon",
          "date": "Aug 17",
          "slots": [
            {
              "time": "9:00 AM",
              "status": "busy",
              "note": "Team sync"
            },
            {
              "time": "12:30 PM",
              "status": "open"
            },
            {
              "time": "5:30 PM",
              "status": "best",
              "note": "Quiet slot"
            }
          ]
        },
        {
          "dayName": "Tue",
          "date": "Aug 18",
          "slots": [
            {
              "time": "8:00 AM",
              "status": "open"
            },
            {
              "time": "11:00 AM",
              "status": "busy",
              "note": "Dentist"
            },
            {
              "time": "3:00 PM",
              "status": "open"
            }
          ]
        },
        {
          "dayName": "Wed",
          "date": "Aug 19",
          "slots": [
            {
              "time": "9:00 AM",
              "status": "busy",
              "note": "Team sync"
            },
            {
              "time": "1:00 PM",
              "status": "busy",
              "note": "Lunch w/ client"
            },
            {
              "time": "6:00 PM",
              "status": "open"
            }
          ]
        },
        {
          "dayName": "Thu",
          "date": "Aug 20",
          "slots": [
            {
              "time": "7:30 AM",
              "status": "best",
              "note": "Before work"
            },
            {
              "time": "2:00 PM",
              "status": "open"
            },
            {
              "time": "4:00 PM",
              "status": "busy",
              "note": "1:1 w/ manager"
            }
          ]
        },
        {
          "dayName": "Fri",
          "date": "Aug 21",
          "slots": [
            {
              "time": "9:00 AM",
              "status": "busy",
              "note": "Team sync"
            },
            {
              "time": "12:00 PM",
              "status": "open"
            },
            {
              "time": "5:00 PM",
              "status": "busy",
              "note": "Happy hour"
            }
          ]
        },
        {
          "dayName": "Sat",
          "date": "Aug 22",
          "slots": [
            {
              "time": "10:00 AM",
              "status": "best",
              "note": "Wide open"
            },
            {
              "time": "2:00 PM",
              "status": "open"
            }
          ]
        },
        {
          "dayName": "Sun",
          "date": "Aug 23",
          "slots": [
            {
              "time": "11:00 AM",
              "status": "open"
            },
            {
              "time": "4:00 PM",
              "status": "open"
            }
          ]
        }
      ]
    }
  }
}
event: message
{
  "type": "tool.done",
  "seq": 402,
  "turnId": "turn_1",
  "toolCallId": "call_2",
  "content": [
    {
      "type": "text",
      "text": "Contract violation in ggui_render:\n- days[0].slots[0].note: Undeclared field 'note' at 'days[0].slots[0]'. Declared keys: [time, status]. (expected: <declared key>, got: string)\n- days[0].slots[2].note: Undeclared field 'note' at 'days[0].slots[2]'. Declared keys: [time, status]. (expected: <declared key>, got: string)\n- days[0].slots[2].status: must be equal to one of the allowed values (expected: \"open\"|\"busy\", got: \"best\")\n- days[1].slots[1].note: Undeclared field 'note' at 'days[1].slots[1]'. Declared keys: [time, status]. (expected: <declared key>, got: string)\n- days[2].slots[0].note: Undeclared field 'note' at 'days[2].slots[0]'. Declared keys: [time, status]. (expected: <declared key>, got: string)\n- days[2].slots[1].note: Undeclared field 'note' at 'days[2].slots[1]'. Declared keys: [time, status]. (expected: <declared key>, got: string)\n- days[3].slots[0].note: Undeclared field 'note' at 'days[3].slots[0]'. Declared keys: [time, status]. (expected: <declared key>, got: string)\n- days[3].slots[0].status: must be equal to one of the allowed values (expected: \"open\"|\"busy\", got: \"best\")\n- days[3].slots[2].note: Undeclared field 'note' at 'days[3].slots[2]'. Declared keys: [time, status]. (expected: <declared key>, got: string)\n- days[4].slots[0].note: Undeclared field 'note' at 'days[4].slots[0]'. Declared keys: [time, status]. (expected: <declared key>, got: string)\n- days[4].slots[2].note: Undeclared field 'note' at 'days[4].slots[2]'. Declared keys: [time, status]. (expected: <declared key>, got: string)\n- days[5].slots[0].note: Undeclared field 'note' at 'days[5].slots[0]'. Declared keys: [time, status]. (expected: <declared key>, got: string)\n- days[5].slots[0].status: must be equal to one of the allowed values (expected: \"open\"|\"busy\", got: \"best\")"
    }
  ],
  "outcome": "error",
  "isError": true,
  "messageId": "toolu_01NEymr4EEzVm9PhWJjRZB8s:result"
}
event: message
{
  "type": "tool.args.assembled",
  "seq": 593,
  "toolCallId": "call_3",
  "input": {
    "handshakeId": "handshake_1",
    "props": {
      "personName": "Sam",
      "service": "Haircut (30 min)",
      "weekLabel": "Aug 17–23, 2026",
      "days": [
        {
          "dayName": "Mon",
          "date": "Aug 17",
          "slots": [
            {
              "time": "9:00 AM – Team sync",
              "status": "busy"
            },
            {
              "time": "12:30 PM",
              "status": "open"
            },
            {
              "time": "5:30 PM",
              "status": "open"
            }
          ]
        },
        {
          "dayName": "Tue",
          "date": "Aug 18",
          "slots": [
            {
              "time": "8:00 AM",
              "status": "open"
            },
            {
              "time": "11:00 AM – Dentist",
              "status": "busy"
            },
            {
              "time": "3:00 PM",
              "status": "open"
            }
          ]
        },
        {
          "dayName": "Wed",
          "date": "Aug 19",
          "slots": [
            {
              "time": "9:00 AM – Team sync",
              "status": "busy"
            },
            {
              "time": "1:00 PM – Lunch w/ client",
              "status": "busy"
            },
            {
              "time": "6:00 PM",
              "status": "open"
            }
          ]
        },
        {
          "dayName": "Thu",
          "date": "Aug 20",
          "slots": [
            {
              "time": "7:30 AM",
              "status": "open"
            },
            {
              "time": "2:00 PM",
              "status": "open"
            },
            {
              "time": "4:00 PM – 1:1 w/ manager",
              "status": "busy"
            }
          ]
        },
        {
          "dayName": "Fri",
          "date": "Aug 21",
          "slots": [
            {
              "time": "9:00 AM – Team sync",
              "status": "busy"
            },
            {
              "time": "12:00 PM",
              "status": "open"
            },
            {
              "time": "5:00 PM – Happy hour",
              "status": "busy"
            }
          ]
        },
        {
          "dayName": "Sat",
          "date": "Aug 22",
          "slots": [
            {
              "time": "10:00 AM",
              "status": "open"
            },
            {
              "time": "2:00 PM",
              "status": "open"
            }
          ]
        },
        {
          "dayName": "Sun",
          "date": "Aug 23",
          "slots": [
            {
              "time": "11:00 AM",
              "status": "open"
            },
            {
              "time": "4:00 PM",
              "status": "open"
            }
          ]
        }
      ]
    }
  }
}
event: message
{
  "type": "tool.done",
  "seq": 595,
  "turnId": "turn_1",
  "toolCallId": "call_3",
  "content": [
    {
      "type": "text",
      "text": "{\"sessionId\":\"session_3\",\"resourceUri\":\"ui://ggui/render/render_1/token_removed\",\"action\":\"create\",\"contractHash\":\"c67119865ad0b290\",\"blueprintId\":\"blueprint_1\",\"variantKey\":\"44136fa355b3678a\",\"cache\":{\"hit\":true,\"similarity\":1,\"cachedBlueprintId\":\"blueprint_1\",\"llmCallsAvoided\":1,\"kind\":\"full-template\",\"reason\":\"full-template: reused stored blueprint bp_id_4; 1 generation call avoided\"},\"nextStep\":{\"tool\":\"ggui_consume\",\"description\":\"The UI has interactive actions. After this turn's other tool calls, you may long-poll once (waits up to 25s) to catch an immediate gesture; if `events` comes back empty, end your turn — later gestures arrive as new user messages carrying their own consume directive.\",\"example\":\"ggui_consume({ sessionId: \\\"render_id_11\\\", timeout: 25 })\",\"args\":{\"sessionId\":\"session_3\",\"timeout\":25}}}"
    }
  ],
  "outcome": "ok",
  "isError": false,
  "messageId": "toolu_01LpNXD896Z473kXjazJvpmM:result",
  "structuredContent": {
    "sessionId": "session_3",
    "resourceUri": "ui://ggui/render/render_1/token_removed",
    "action": "create",
    "contractHash": "c67119865ad0b290",
    "blueprintId": "blueprint_1",
    "variantKey": "44136fa355b3678a",
    "cache": {
      "hit": true,
      "similarity": 1,
      "cachedBlueprintId": "blueprint_1",
      "llmCallsAvoided": 1,
      "kind": "full-template",
      "reason": "full-template: reused stored blueprint bp_id_4; 1 generation call avoided"
    },
    "nextStep": {
      "tool": "ggui_consume",
      "description": "The UI has interactive actions. After this turn's other tool calls, you may long-poll once (waits up to 25s) to catch an immediate gesture; if `events` comes back empty, end your turn — later gestures arrive as new user messages carrying their own consume directive.",
      "example": "ggui_consume({ sessionId: \"render_id_11\", timeout: 25 })",
      "args": {
        "sessionId": "session_3",
        "timeout": 25
      }
    }
  }
}

Ids are stable placeholders (thread_1, call_2); credentials were removed at capture, never recorded.

The host mounts the material; the runtime inside the frame answers the MCP-Apps ui/initialize handshake, posts ggui:renderer-ready, and opens the live WebSocket named in the bootstrap. This beat happens inside the host page, not on the agent’s SSE stream — which is why there is no wire panel here: the recorded stream shows the agent’s side only. The exact boot flow and host obligations are specified in Bootstrap handshake → the boot flow.

5 · The gesture (and how a real one arrives)

Section titled “5 · The gesture (and how a real one arrives)”

A surface that declares an action routes a tap this way: the visitor taps, the runtime emits an ActionEnvelope on the live channel, and the agent drains it with ggui_consume and reasons about it on its next turn — see Envelopes → ActionEnvelope.

This recording was made by a script that sent no gesture, so there is no ggui_consume frame to show and nothing here claims one happened. What the frames do show is the end of the turn: the agent hands control back with a next step, offering to take the conversation further.

On the wire — the turn closes 4 frames · turn 1
event: message
{
  "type": "text.delta",
  "seq": 626,
  "id": "id_17",
  "messageId": "msg_011Ce6Cv5jSLS8NAcrfC7vEH",
  "delta": "Here's",
  "turnId": "turn_1"
}
event: message
{
  "type": "text.delta",
  "seq": 627,
  "id": "id_17",
  "messageId": "msg_011Ce6Cv5jSLS8NAcrfC7vEH",
  "delta": " Sam's week 🗓️ — plenty of open pockets for a quick 30-min haircut. Best bets: **Sat 10am** (wide open) or **Th",
  "turnId": "turn_1"
}
event: message
{
  "type": "text.delta",
  "seq": 628,
  "id": "id_17",
  "messageId": "msg_011Ce6Cv5jSLS8NAcrfC7vEH",
  "delta": "u 7:30am** before work — both dodge the busy stretches. Tap any slot to try it on.",
  "turnId": "turn_1"
}
event: done
{
  "stopReason": "end_turn",
  "threadId": "thread_1",
  "userSeq": 1,
  "agentSeq": 10
}

Ids are stable placeholders (thread_1, call_2); credentials were removed at capture, never recorded.

6 · Turn 2 — a second ask on the same thread

Section titled “6 · Turn 2 — a second ask on the same thread”

“Compare Thursday afternoon against Friday morning for me.” The invoke carries the threadId learned in beat 1, so the agent has the whole conversation. The panel below opens turn 2 with its own session frame — naming the same thread_1 you read in beat 1, and the numbering is what makes that evidence rather than coincidence: one placeholder map spans the whole recording, so turn 2's ids continue turn 1's instead of restarting at _1 (blueprint_2, call_5, handshake_2). Equal placeholders therefore mean equal ids, and this thread is on screen four times across the panels on this page — both session frames and both done frames, all agreeing. The handshake that follows it negotiates a contract for the new ask.

On the wire — turn 2 opens 3 frames · turn 2
event: session
{
  "sessionId": "session_1",
  "userId": "user_1",
  "authMode": "anonymous",
  "threadId": "thread_1"
}
event: message
{
  "type": "tool.start",
  "seq": 9,
  "turnId": "turn_2",
  "toolCallId": "call_5",
  "name": "mcp__ggui__ggui_handshake",
  "index": 1,
  "messageId": "msg_011Ce6CvocqrmNejesdrwJRT",
  "providerExecuted": false
}
event: message
{
  "type": "tool.done",
  "seq": 160,
  "turnId": "turn_2",
  "toolCallId": "call_5",
  "content": [
    {
      "type": "text",
      "text": "{\"handshakeId\":\"handshake_2\",\"action\":\"reuse\",\"suggestion\":{\"origin\":\"cache\",\"rationale\":\"match-semantic: judge matched bp_id_21 (cosine=0.58, confidence=0.82) — Candidate matches the same task (comparing Thu afternoon vs Fri morning haircut slots) and broad UI shape (columns-based comparison layout with selectSlot action), though action signature differs slightly (column vs day parameter).\",\"blueprintMeta\":{\"blueprintId\":\"blueprint_2\",\"contractHash\":\"722e6679a8a60995\",\"codeHash\":\"e121e6c267b27ce06ef132d1dfb1700f9252ca18c095ec8a54e9f559e011008f\",\"source\":{\"kind\":\"llm\",\"generator\":\"ui-gen-default-haiku-4-5\",\"model\":\"claude-haiku-4-5-20251001\"},\"variance\":{},\"selectedReason\":\"match-semantic: judge matched bp_id_21 (cosine=0.58, confidence=0.82) — Candidate matches the same task (comparing Thu afternoon vs Fri morning haircut slots) and broad UI shape (columns-based comparison layout with selectSlot action), though action signature differs slightly (column vs day parameter).\"},\"proposedContractSummary\":\"slots=∅; actions=selectSlot(column,time); streams=∅; props=columns:array!,recommendation:string,title:string!\"},\"nextStep\":{\"tool\":\"ggui_render\",\"example\":\"ggui_render({\\\"handshakeId\\\":\\\"handshake_2\\\",\\\"props\\\":{\\\"title\\\":\\\"Thursday PM vs Friday AM\\\",\\\"columns\\\":[{\\\"label\\\":\\\"\\\",\\\"date\\\":\\\"\\\",\\\"window\\\":\\\"\\\",\\\"slots\\\":[{\\\"time\\\":\\\"\\\",\\\"available\\\":false,\\\"note\\\":\\\"\\\"}],\\\"pros\\\":[\\\"\\\"],\\\"cons\\\":[\\\"\\\"]}],\\\"recommendation\\\":\\\"Friday morning has more open slots and less rush.\\\"}})\"}}"
    }
  ],
  "outcome": "ok",
  "isError": false,
  "messageId": "toolu_01AsH5PjiN7YFrMNB5zXFjFV:result",
  "structuredContent": {
    "handshakeId": "handshake_2",
    "action": "reuse",
    "suggestion": {
      "origin": "cache",
      "rationale": "match-semantic: judge matched bp_id_21 (cosine=0.58, confidence=0.82) — Candidate matches the same task (comparing Thu afternoon vs Fri morning haircut slots) and broad UI shape (columns-based comparison layout with selectSlot action), though action signature differs slightly (column vs day parameter).",
      "blueprintMeta": {
        "blueprintId": "blueprint_2",
        "contractHash": "722e6679a8a60995",
        "codeHash": "e121e6c267b27ce06ef132d1dfb1700f9252ca18c095ec8a54e9f559e011008f",
        "source": {
          "kind": "llm",
          "generator": "ui-gen-default-haiku-4-5",
          "model": "claude-haiku-4-5-20251001"
        },
        "variance": {},
        "selectedReason": "match-semantic: judge matched bp_id_21 (cosine=0.58, confidence=0.82) — Candidate matches the same task (comparing Thu afternoon vs Fri morning haircut slots) and broad UI shape (columns-based comparison layout with selectSlot action), though action signature differs slightly (column vs day parameter)."
      },
      "proposedContractSummary": "slots=∅; actions=selectSlot(column,time); streams=∅; props=columns:array!,recommendation:string,title:string!"
    },
    "nextStep": {
      "tool": "ggui_render",
      "example": "ggui_render({\"handshakeId\":\"handshake_2\",\"props\":{\"title\":\"Thursday PM vs Friday AM\",\"columns\":[{\"label\":\"\",\"date\":\"\",\"window\":\"\",\"slots\":[{\"time\":\"\",\"available\":false,\"note\":\"\"}],\"pros\":[\"\"],\"cons\":[\"\"]}],\"recommendation\":\"Friday morning has more open slots and less rush.\"}})"
    }
  }
}

Ids are stable placeholders (thread_1, call_2); credentials were removed at capture, never recorded.

One caution if you open the raw fixtures: two different id spaces share the key name threadId. The session frame's is the platform's durable conversation thread — the one a replayed turn rejoins, and the one every claim on this page is about. The message-stream frames (turn.start, message.start) carry the AgJSON stream's own per-turn thread id under the same key: thread_2 in turn 1 and thread_3 in turn 2, neither of them thread_1. Different spaces, same spelling; compare session frames to session frames.

This recording shows a second render. The agent negotiated again — a second ggui_handshake on the same thread — and rendered a fresh comparison surface. That is a legitimate choice for a different surface: a two-option slot comparison is not the week grid. The contract check from beat 3 applies here too.

This time nothing was refused — the agent's props conformed on the first call, so the panel holds one call and one successful result. Beat 3 is where you can see the check turn a call back.

On the wire — turn 2 renders 2 frames · turn 2
event: message
{
  "type": "tool.args.assembled",
  "seq": 305,
  "toolCallId": "call_6",
  "input": {
    "handshakeId": "handshake_2",
    "props": {
      "title": "Thursday PM vs Friday AM",
      "columns": [
        {
          "label": "Thursday",
          "date": "Aug 20",
          "window": "Afternoon",
          "slots": [
            {
              "time": "1:00pm",
              "available": false,
              "note": "booked"
            },
            {
              "time": "2:30pm",
              "available": true,
              "note": "tight before 3pm rush"
            },
            {
              "time": "4:00pm",
              "available": true,
              "note": "open"
            }
          ],
          "pros": [
            "Later start, no rush in the morning",
            "Still same-day results"
          ],
          "cons": [
            "Afternoons fill up fast",
            "Less buffer if it runs long"
          ]
        },
        {
          "label": "Friday",
          "date": "Aug 21",
          "window": "Morning",
          "slots": [
            {
              "time": "9:00am",
              "available": true,
              "note": "wide open"
            },
            {
              "time": "10:00am",
              "available": true,
              "note": "wide open"
            },
            {
              "time": "11:30am",
              "available": false,
              "note": "booked"
            }
          ],
          "pros": [
            "Fresh cut heading into the weekend",
            "More open slots to choose from"
          ],
          "cons": [
            "Early start needed"
          ]
        }
      ],
      "recommendation": "Friday morning edges it out — more open slots and a fresh cut right before the weekend."
    }
  }
}
event: message
{
  "type": "tool.done",
  "seq": 307,
  "turnId": "turn_2",
  "toolCallId": "call_6",
  "content": [
    {
      "type": "text",
      "text": "{\"sessionId\":\"session_5\",\"resourceUri\":\"ui://ggui/render/render_2/token_removed\",\"action\":\"create\",\"contractHash\":\"722e6679a8a60995\",\"blueprintId\":\"blueprint_2\",\"variantKey\":\"44136fa355b3678a\",\"cache\":{\"hit\":true,\"similarity\":1,\"cachedBlueprintId\":\"blueprint_2\",\"llmCallsAvoided\":1,\"kind\":\"full-template\",\"reason\":\"full-template: reused stored blueprint bp_id_21; 1 generation call avoided\"},\"nextStep\":{\"tool\":\"ggui_consume\",\"description\":\"The UI has interactive actions. After this turn's other tool calls, you may long-poll once (waits up to 25s) to catch an immediate gesture; if `events` comes back empty, end your turn — later gestures arrive as new user messages carrying their own consume directive.\",\"example\":\"ggui_consume({ sessionId: \\\"render_id_25\\\", timeout: 25 })\",\"args\":{\"sessionId\":\"session_5\",\"timeout\":25}}}"
    }
  ],
  "outcome": "ok",
  "isError": false,
  "messageId": "toolu_018WYx5zJgWPcvLTgSDF4Mem:result",
  "structuredContent": {
    "sessionId": "session_5",
    "resourceUri": "ui://ggui/render/render_2/token_removed",
    "action": "create",
    "contractHash": "722e6679a8a60995",
    "blueprintId": "blueprint_2",
    "variantKey": "44136fa355b3678a",
    "cache": {
      "hit": true,
      "similarity": 1,
      "cachedBlueprintId": "blueprint_2",
      "llmCallsAvoided": 1,
      "kind": "full-template",
      "reason": "full-template: reused stored blueprint bp_id_21; 1 generation call avoided"
    },
    "nextStep": {
      "tool": "ggui_consume",
      "description": "The UI has interactive actions. After this turn's other tool calls, you may long-poll once (waits up to 25s) to catch an immediate gesture; if `events` comes back empty, end your turn — later gestures arrive as new user messages carrying their own consume directive.",
      "example": "ggui_consume({ sessionId: \"render_id_25\", timeout: 25 })",
      "args": {
        "sessionId": "session_5",
        "timeout": 25
      }
    }
  }
}

Ids are stable placeholders (thread_1, call_2); credentials were removed at capture, never recorded.

On the wire — turn 2 closes 6 frames · turn 2
event: message
{
  "type": "text.delta",
  "seq": 333,
  "id": "id_30",
  "messageId": "msg_011Ce6Cyy4TShEkvnFTYou1N",
  "delta": "Side",
  "turnId": "turn_2"
}
event: message
{
  "type": "text.delta",
  "seq": 334,
  "id": "id_30",
  "messageId": "msg_011Ce6Cyy4TShEkvnFTYou1N",
  "delta": "-",
  "turnId": "turn_2"
}
event: message
{
  "type": "text.delta",
  "seq": 335,
  "id": "id_30",
  "messageId": "msg_011Ce6Cyy4TShEkvnFTYou1N",
  "delta": "by-side is up 👆 — **Friday morning** narrowly wins (more open slots, f",
  "turnId": "turn_2"
}
event: message
{
  "type": "text.delta",
  "seq": 336,
  "id": "id_30",
  "messageId": "msg_011Ce6Cyy4TShEkvnFTYou1N",
  "delta": "resh cut before the weekend), while **Thursday afternoon** works if you'd rather sleep in but has less breathing room. Tap a slot to pre",
  "turnId": "turn_2"
}
event: message
{
  "type": "text.delta",
  "seq": 337,
  "id": "id_30",
  "messageId": "msg_011Ce6Cyy4TShEkvnFTYou1N",
  "delta": "view it!",
  "turnId": "turn_2"
}
event: done
{
  "stopReason": "end_turn",
  "threadId": "thread_1",
  "userSeq": 12,
  "agentSeq": 19
}

Ids are stable placeholders (thread_1, call_2); credentials were removed at capture, never recorded.

7 · What a conforming implementation had to do

Section titled “7 · What a conforming implementation had to do”

At each beat above, an implementation either did the specified thing or the turn would not have completed: answered the handshake with a conforming contract (beat 2), validated the committed props against that contract before mounting anything (beat 3), returned a durable identity the host resolves to mount material through the read plane (beat 3), booted and negotiated inside the frame (beat 4), and — for a surface that declares an action — would route a gesture back as an ActionEnvelope (beat 5).

Validation is the one of those you can watch happen rather than take on faith: one call in this recording was refused for violating the contract, and the repair that followed is in beat 3's panel, one frame later.

The arbiter of “conforming” is not this page — it is the conformance kit; the fixtures behind these panels are the kind of input it runs.

  • See it live: guuey.com/demos/scheduler — run the same ask and watch a negotiation of your own.
  • Check your implementation: take the recording itself — turn 1, turn 2, and its manifest — and feed the frames to your host or server as fixtures. They are the same files these panels are built from, ids placeholdered, nothing else altered.
  • Build your own agent on the protocol: Build an agent on hosted ggui or self-host.