Apps and Screens
Compose reusable Screens into an ordered agent job, publish it, and run it from the CLI or MCP.
An App is one Markdown agent job plus an ordered list of Screens. A Screen is the reusable visual contract previously called a Rect template. A live Screen still uses the existing Rect instance runtime and actions.
This boundary is intentional:
- App instructions decide what job the agent performs and when to ask the user a question.
- Each Screen spec decides how one ViewModel is displayed and safely changed.
- An App run groups normal Screen instances by key and position. There is no intake-to-view mapping language; the agent reads one Screen and dispatches declared actions on another.
Author an App locally
Put the job in Markdown and the bindings in JSON:
# Purchase comparison job
Wait for the `request` Screen to be submitted. Read its document blocks as one
purchase request, research the named candidates, and fill the `comparison`
Screen through its declared actions. If a required fact is missing, keep the
request Screen visible and ask one concise question. Call `setVerdict` last,
then explicitly select the `comparison` Screen and report the update in chat.{
"slug": "purchase-research",
"name": "Purchase Research",
"description": "Collect a purchase request and return a researched comparison board.",
"visibility": "unlisted",
"instructionsFile": "./app.md",
"screens": [
{
"key": "request",
"screen": "annotated-editor",
"initialViewModel": {
"document": {
"title": "What are you deciding?",
"blocks": [
{
"id": "draft",
"type": "paragraph",
"content": "",
"children": []
}
],
"version": 1,
"titleRevision": 1,
"blockRevisions": { "draft": 1 },
"updatedAt": "2026-08-11T00:00:00.000Z"
},
"annotations": [],
"submission": { "status": "in_review" }
}
},
{
"key": "comparison",
"screen": "purchase-compare",
"initialViewModel": {
"title": "Comparison in progress",
"priorities": [],
"costRows": [],
"specSections": [],
"candidates": [],
"verdict": null
}
}
]
}screen accepts a visible Screen slug or id. During publication the CLI
resolves it to both the stable Screen id and its current immutable version id;
that exact pair is stored in the App version. You can instead provide
screenId and screenVersionId explicitly.
When visibility is omitted, Apps default to unlisted: anyone with the link
can open the App and start a new private run, but the App is not listed in the
public catalog. Set it explicitly to private or public when needed.
Every Rect Screen exposes a required stateSchema. Prepare each reusable
initialViewModel from the pinned Screen's schema and example: remove sample
subject matter and completed or run-specific state, but preserve every required
structural field. App publication compiles the pinned schema and rejects a
missing or invalid schema or an initial ViewModel that does not conform to it.
Bound Screens must belong to the same workspace as the App. This keeps the published definition and deletion boundary predictable. Private App runs stay in that workspace. An unlisted or public App can also create an isolated run and Screen instances in a recipient workspace after the recipient explicitly confirms the third-party instructions.
External public or official Apps may appear when discovery explicitly includes the public catalog. Inspect third-party instructions before confirming them:
rect app publish app.json --account acme
rect app list --account acme
rect app spec <app-id>
rect app launch <app-id> --account acme --open
rect app launch <shared-app-id> --account acme --confirm-third-party --openUnlisted and public Apps have a stable browser sharing URL at /a/<app-id>.
Unlisted Apps stay out of public catalog results, while remaining visible in
their publisher workspace. Starting
from that page requires sign-in, asks for a destination when the recipient has
multiple workspaces, and creates fresh workspace-only run data. The publisher
does not gain access to the recipient's run, Screen state, attachments, or
Agent chat by sharing the App; existing workspace membership still applies.
The publish command prints this sharing URL for unlisted and public Apps.
Republishing the same App slug creates a new immutable App version. Existing runs keep their original App version and Screen versions.
Drive a run from an external agent
The CLI loop is:
rect app read <run-id>
rect spec <request-screen-instance-id>
rect read <request-screen-instance-id>
rect app attachment link <run-id> <attachment-id> --screen comparison
rect spec <comparison-screen-instance-id>
rect dispatch <comparison-screen-instance-id> setBoardInfo '{...}'
rect dispatch <comparison-screen-instance-id> setVerdict '{...}'
rect app select-screen <run-id> --screen comparison \
--expected-revision <revision>
rect app read <run-id>The MCP equivalents are rect_get_app_run, rect_get_spec,
rect_get_result, rect_attach_to_screen, and rect_dispatch. The App run
response contains the Markdown instructions, every ordered Screen's handle, and
ready attachment summaries from every Screen in that run. These summaries have
ids and metadata, never bytes or signed URLs. Read only the Screen you need with
rect_get_result or rect read; App responses intentionally do not inline
every ViewModel.
When a later Screen needs a file uploaded in an earlier Screen, link it explicitly before calling the later Screen's import action:
rect app attachment link <run-id> <attachment-id> --screen pptx-review
rect dispatch <pptx-review-instance-id> requestPptxImport \
'{"attachmentId":"<attachment-id>"}'Linking does not copy the file. It adds a target-scoped reference to the target
Screen's host-owned $attachments registry. Only Screens in the same App run
can share the reference; standalone Rects and other runs cannot use it.
Use rect_select_app_screen (or rect app select-screen) to choose the Screen
shown to the user:
rect app select-screen <run-id> \
--screen request \
--expected-revision 2Every change is explicit: a Rect action can change one Screen's ViewModel and completion status, but it never changes the active App Screen. Screen order is presentation metadata, not a runtime dependency graph. The agent chooses the appropriate Screen and reports progress or questions in chat.
Runtime state and navigation
activeScreenKey identifies the user-facing Screen. Launch sets it to position
zero so the predefined intake ViewModel appears immediately. The external agent
changes it only through the Screen selection tool or CLI command. Buttons inside
a Screen update that Screen or signal the agent; they do not navigate the App
themselves.
The workspace shows an unread update badge after an App-scoped assistant response finishes. Opening that App clears the badge for that user. Progress, questions, completion, and failures stay in the conversation instead of an agent-selected lifecycle field. Recent work shows a concise outcome extracted from the first successful Agent response rather than the raw opening prompt. The run owner can edit that summary inline; an edited or existing summary is never overwritten by later Agent turns.
The public URL remains /r/<id>. The server resolves the id as an App run
first; otherwise it renders the existing standalone Screen instance. Child
Screens keep their own /r/<instance-id> URLs and existing access controls.