rect.sh

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:

app.md
# 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.
app.json
{
  "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 --open

Unlisted 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 2

Every 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.

On this page