---
title: Mobile — Threads & Chat
description: The Threads tab, thread detail, HITL confirmation cards, streaming message render, message input with workspace picker, quick actions, and voice dictation in the ThinkWork mobile app.
---

import { Aside } from '@astrojs/starlight/components';

Threads are the heart of the mobile app. The **Threads** tab shows the user's threads, each thread opens into a chat view with a streaming message timeline, and the message input supports workspace selection, voice dictation, and quick actions. When an agent pauses for human confirmation, that request appears in the same Threads surface instead of moving the user to a separate queue.

## The Threads tab

**Tab:** `(tabs)/index`

The Threads tab is the user's list of open and recent threads. Each row shows the thread title, the agent handling it, a snippet of the latest message, and a status indicator. Threads that are currently running show a subtle shimmer effect so the user can tell an agent is thinking in real time.

The Threads tab supports:

- **Filtering** — by status, by agent, by channel (chat, task, email, Slack, GitHub)
- **Search** — by title or recent message content
- **Real-time updates** — AppSync subscriptions keep the list in sync without a manual refresh
- **Pull-to-refresh** — a backup path for when the subscription is warming up

Rows are rendered by two components: `ThreadRow` for chat threads and `TaskRow` for external-task threads. The user doesn't have to care about the difference; the list is unified.

### Human-in-the-loop priority

Workspace orchestration can pause an agent run when the agent needs a human decision before continuing. The mobile app treats that as a thread state, not as a separate task system.

When a thread has a pending review request:

- The thread sorts above ordinary non-HITL threads, even if another thread has newer activity.
- The row shows a `Needs answer` badge.
- The preview changes to explain that the agent is waiting for confirmation.
- The Threads tab badge switches to the count of visible HITL threads before showing ordinary unread counts.

This makes the user's next required decision visible without splitting the conversation across different tabs.

### Sub-agent reviews route through the parent chain

If a sub-agent of one of your agents pauses for review, that review surfaces in **your** mobile threads — even though you're not a participant in the sub-agent's thread directly. The platform classifies each pending review by walking `agents.parent_agent_id` from the run's agent until it finds an ancestor with a `human_pair_id` set; that user is the responsible human, and the review appears on their mobile.

Sub-agent rows carry an explicit `Sub-agent <agent name> needs your input on <target path>` label so you can tell whose work the agent is asking about. The thread detail confirmation card that opens from one of these rows is the parent agent's thread, not the sub-agent's — your decision wakes the sub-agent run, and the parent agent continues from there.

Reviews where the chain has no human pair anywhere (system agents like the eval test runtime, or genuinely orphaned configurations) do not appear on mobile at all. They surface in the admin app's Inbox instead. See [Workspace Orchestration → Human review flow](/concepts/agents/workspace-orchestration/#human-review-flow) for the full routing model.

## Thread detail

**Route:** `/thread/[threadId]`

Tapping a thread opens the chat view. The layout top-to-bottom:

1. **Message timeline.** User and agent messages scroll in reverse-chronological order with user messages right-aligned and agent messages left-aligned.
2. **Message input footer.** A multi-line input with supporting controls.

The timeline renders markdown via a custom `MarkdownMessage` component that handles code blocks, inline code, tables, and streaming text. In-flight agent messages render character-by-character as tokens stream from the model, so the user sees the response form in real time.

### Streaming and shimmer

Three visual states communicate agent activity:

- **Idle** — no activity, no indicator
- **Thinking** — the agent is working but hasn't started streaming text yet. A `ShimmerProcessing` indicator shows near the bottom of the timeline with a label like "Thinking…" or "Retrieving memory…"
- **Streaming** — text tokens are arriving. A `ShimmerText` render replaces the static message content with animated cursor-style text that fills in as tokens land

Subscriptions drive all three. `useThreadUpdatedSubscription` watches for thread-level state changes; `useNewMessageSubscription` delivers new message events and streaming chunks as they come in.

### Tool-call inline render

When an agent calls a tool during a turn, the tool call renders inline in the timeline as a collapsible row: tool name, input preview, output preview. This is the mobile mirror of the [Admin app's thread detail turn-level audit view](/applications/admin/threads/#thread-detail), scoped to what's relevant to an end user.

Tool calls with rich output — for example, a tool returning an external task envelope — use the GenUI registry to render a custom block inline, instead of just plain text. The GenUI registry is how the mobile app stays extensible without shipping a new build for every new tool.

## Human-in-the-loop confirmations

When the user opens a thread that is waiting for review, the thread detail screen shows a confirmation card near the top of the timeline. The card is the handoff point between the agent and the user.

The card shows:

- The workspace target path the agent is waiting on.
- The review text written by the orchestration layer.
- A compact summary of proposed changes when the review includes them.
- An optional note field for context the user wants to send back to the agent.
- Actions for the decision the user wants to take.

The available actions match the state of the workspace run:

| Action | Meaning |
|---|---|
| `Approve` | Accept the review, include the optional note if present, and clear the agent to continue. |
| `Continue` | Resume a pending workspace run without recording a fresh approval decision. This is used when the run is already queued or only needs a nudge to continue. |
| `Reject` | Cancel the workspace run. The agent is not resumed from that review request. |

After a successful action, the app confirms the decision. For `Approve` and `Continue`, the thread is marked active while the agent resumes in the same conversation. If the review changed after the user opened it, the app asks the user to refresh before deciding so they do not answer stale review text.

### Under the hood

The mobile HITL surface is backed by the workspace review GraphQL contract:

- `agentWorkspaceReviews` lists pending reviews, filtered by `status: "awaiting_review"` and `responsibleUserId: caller.userId`. The `responsibleUserId` filter is what scopes the list to the signed-in user — the resolver derives it by walking the `parent_agent_id` chain server-side, so the same query naturally captures both direct-agent and sub-agent reviews routed to this user.
- `agentWorkspaceReview` loads the review body, ETag, event history, and proposed-change summary for the thread detail card.
- `acceptAgentWorkspaceReview`, `resumeAgentWorkspaceRun`, and `cancelAgentWorkspaceReview` record the user's decision.

Those decisions are auditable workspace events. Approval and resume decisions queue a `workspace_event` wakeup so the agent continues the same thread with the review response in context. Cancellation settles the run without waking the agent.

The mobile app does not hide HITL state locally as a workaround. The list and detail screens derive visibility from `agentWorkspaceReviews(status: "awaiting_review", responsibleUserId: caller.userId)`; once the backend run leaves that state — or routes to a different responsible human — the amber row treatment and Threads tab HITL count clear naturally.

Protected orchestration paths are still protected. Review decisions should go through the GraphQL mutations above, not through generic workspace file writes.

## Message input

The message input footer is more than a plain text field. It has several stacked controls:

### Workspace picker

A chip at the top of the input shows the current workspace. Tapping it opens the `WorkspacePickerSheet`, a bottom sheet listing the workspaces available for the active agent. Selecting one scopes the next message to that workspace — useful when an agent handles multiple project-like contexts.

### Voice dictation

A microphone button next to the input opens the `VoiceDictationBar`. Speech is transcribed locally via iOS speech recognition and streamed into the input as text. Tapping the mic again commits the transcription and clears the bar.

### Quick actions

A lightning-bolt button opens the quick-actions sheet: a customizable list of one-tap prompts the user has saved ("summarize", "plan my day", "draft a reply"). Quick actions are per-user and sync back to the admin app through the same GraphQL schema — see [Admin — Agents](/applications/admin/agents/) for the authoring side.

### Plus button

A plus button opens the workspace-picker sheet (the same one the workspace chip opens), acting as a shortcut for users who want to change workspace before composing.

## Real-time, not polling

Everything on the Threads list and thread detail screens is driven by AppSync subscriptions. The mobile app does **not** poll. When the user pulls to refresh, that's a backup path — the subscription should already have delivered the update by the time the user's thumb hits the screen.

When the app goes to background and comes back, subscriptions re-attach automatically via the Apollo client's connection management, and any missed updates are backfilled through the GraphQL query cache.

## Offline behavior

The mobile app is online-first: there is no offline message composition queue. If the user is offline, the message input shows a banner and the send button is disabled. Messages in the timeline that were already loaded remain visible.

This is an intentional scope decision for v1. Offline compose adds complexity (local id reconciliation, conflict resolution, optimistic ordering) that does not pay off for the primary use case of agent chat.

## Related pages

- [Threads (concept)](/concepts/threads/) — the thread model at the product level
- [Agents (concept)](/concepts/agents/) — how agents produce the messages the timeline renders
- [Mobile — Integrations & MCP Connect](/applications/mobile/integrations-and-mcp-connect/) — per-user integration and MCP server setup
