Mecatl Studio web UI
Studio is under active development. Its interface, its /api/v1 endpoints, its
configuration variables, and the data it keeps in your browser can change in any
release, with no deprecation period and no migration path. A screen, a URL, or a
setting you rely on today may be renamed or removed in the next version.
Deploy it by an exact version tag rather than latest, read the release notes
before upgrading, and do not build an integration against its HTTP surface yet.
The published image carries org.stacklok.mecatl.studio.stability=early-access
so a deployment can assert on it.
Mecatl Studio is the browser client for a Mecatl deployment. It runs as one
container that serves the web app and a backend for frontend (BFF) from the same
origin. Your browser talks only to the BFF; the BFF holds your credential and
talks to mecated or mecak8s over the same gRPC listener the terminal client
uses. Studio never receives a provider API key.
Every release includes a multi-architecture image for linux/amd64 and
linux/arm64:
ghcr.io/stacklok/mecatl/studio:<VERSION>
The image is signed with keyless Cosign, carries an SPDX SBOM attestation, and has SLSA build provenance. Verify the signature and the provenance before you deploy it:
cosign verify \
--certificate-identity https://github.com/stacklok/mecatl/.github/workflows/release.yml@refs/tags/<VERSION> \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/stacklok/mecatl/studio:<VERSION>
gh attestation verify oci://ghcr.io/stacklok/mecatl/studio:<VERSION> \
--repo stacklok/mecatl \
--signer-workflow stacklok/mecatl/.github/workflows/release.yml \
--source-ref refs/tags/<VERSION>
Both commands accept only a signature that the release.yml workflow in
stacklok/mecatl produced from the <VERSION> tag. To deploy exactly the image
you verified, reference it by digest,
ghcr.io/stacklok/mecatl/studio@sha256:<DIGEST>;
docker buildx imagetools inspect ghcr.io/stacklok/mecatl/studio:<VERSION>
prints the digest.
What Studio offers
Studio exposes the shared capabilities described in the feature guides, and it hides or disables what the connected deployment does not enable:
- Chats: sessions with streamed runs, image attachments, permission approvals, steering, and model and reasoning-effort selection.
- Scheduled: scheduled tasks with a cron builder and fire history, when the deployment enables scheduling.
- Skills: configured and learned skills, learning proposals, and session reflection, when the deployment enables them.
- Settings: personal browser preferences and memory decisions alongside read-only facts about the connected deployment, its providers, and its models.
A global search palette and a keyboard-shortcuts reference page complete the
set. Open /workspace/shortcuts directly or follow Keyboard shortcuts from
Settings > About. The page lists the current browser bindings and features
enabled by the connected deployment.
Browse and review skills
Open Skills to browse the deployment's configured skills. Sort the table by name or description, or filter by name, description, or owner. On screens narrower than 500 pixels, the table becomes a list. If the deployment disables the Skill tool, a banner explains that the agent loads no skills.
Select a skill to see its description and slug. Agent-owned skills also show the owner and active version.
When the deployment enables learned skills, open the Learned view to see agent-drafted versions and their states. If you follow a direct link to this view when learned skills are disabled, Studio explains why it is unavailable.
Select a version to review its body, evidence count, and diff against the version it replaces. The review dialog offers the actions currently available for that version: Activate, Reject, Roll back, or Archive. Each action asks for confirmation. If the skill has changed since the page loaded, Studio reports an error and refreshes the dialog with the current state.
Recent changes shows the 20 most recent lifecycle events. The learned-skills list displays only the first 100 skills.
Client guidance
Studio explains settings ownership in the settings pages: personal choices stay in the browser, while deployment-managed provider, model, posture, and storage facts are read-only. Appearance controls explain theme and palette choices. Delegated-activity panels explain their observed history and incomplete replay. About identifies the Studio image, installed SDK, and connected daemon builds. Ask users for Copy support summary when diagnosing a deployment issue.
Install Studio in your browser
Open your Studio URL over HTTPS in a browser that offers app installation, then
choose the browser's Install app command. On a local workstation, the
loopback URL in the Compose setup also qualifies. Launch the installed Studio
from your app list; it opens Chats at /workspace/chat.
The installed window loads Studio from its origin and needs a connection to the BFF and Mecatl deployment for agent actions. A loaded window reports an unavailable connection if that link drops. For a fully offline launch, the browser handles the missing connection; Studio does not provide an offline starting page.
Use a chat
Open Chats, choose an available model, reasoning effort, and permission mode, then write a message and select Send. You can also set tool access before starting a chat. In an existing chat, change the permission mode in the chat controls; choosing another model creates a fork of that chat. Press Enter to send or Shift+Enter for a new line. Enter used to confirm an input method candidate leaves the message in the composer.
Starter prompts fill the composer for you to review. A link with ?prompt= also
fills it without starting a run. If the link includes send=1, Studio shows the
prompt, target chat, model, and permission mode before you select Send
prompt or Edit prompt. For a new chat, it also shows tool access. Opening
or reloading the link never sends it automatically. If you need to sign in
first, keep the original tab open. Its prompt stays there while the popup or new
tab completes sign-in.
If the selected model supports images, use Attach images to add up to 16 images to one message. Each image can be at most 10 MiB, with a combined limit of 20 MiB. Studio sends images only; other file types are not chat attachments. You can select the microphone button to dictate in a browser that supports speech recognition on a secure origin. Review or edit the recognized text before sending. If recognition is unavailable or microphone access fails, Studio explains the failure and keeps the typed draft available.
Follow and control a run
The transcript streams messages with Markdown, code blocks, and tool activity. Saved inline images appear in the transcript; an external image URL appears as a link you can choose to open. You can scroll back to read earlier messages and use Scroll to latest message to resume following the stream. The status distinguishes sending, working, waiting for approval, and a recorded completed, stopped, canceled, or failed result. If a stream closes without a recorded result, Studio reports an unknown outcome and checks saved history when you reconnect. A history gap does not claim that the run finished.
While Mecatl is working, the composer can queue a text message for the next run or steer the active run. Your Interface preference chooses what Enter does; Shift+Enter selects the other action. You can edit or remove queued messages. Use Stop to cancel the active run. If a run fails and offers Retry, that action retries the failed run without sending the prompt again. Image messages can be sent after the current run ends.
Review tool calls and plans
When a tool needs permission, review its name, reason, and arguments in the approval card. Edit and Write cards show a line diff with display limits when the arguments support one. Open Raw arguments to inspect the arguments, then choose Allow once, Always allow, or Deny. If the arguments are unavailable, only Deny is enabled.
A Plan review card shows the proposed plan and any accompanying note. Choose Approve & run, Auto-accept edits, or Iterate. Iterate rejects the plan and keeps the chat in plan mode. The connected server must support exact plan review for these controls to be available. Otherwise, Studio explains why the review is read-only. If Studio cannot confirm a tool or plan decision, select Refresh activity before deciding again.
Each press of Escape handles one action. An open dialog or menu closes first and returns focus to its trigger. Otherwise, Escape acts in this order:
- Clear a text selection.
- Deny a pending tool request or select Iterate for a plan review, when the review controls are available.
- Close a side panel.
- Stop the active run.
Escape stops at a pending review even when its controls are unavailable.
If none of these actions applies and you have an unsent draft, press Escape, release it, then press it again within half a second to clear the draft. The visible hint shows the available action. Pressing Enter or Space on a focused approval button selects that decision without sending your draft. The Keyboard shortcuts page lists these bindings.
Review external authorization
If a tool needs external authorization, select Review authorization in the chat. Select Open authorization to complete the external step in a new tab, then return to Studio and select Recheck. The panel shows the observed status. Cancel authorization ends the pending handoff. Opening the external page alone does not grant access, and closing the review panel leaves the handoff pending. Open the page from the Studio panel. The Studio authorization link rejects address-bar navigation and links from other sites.
If Studio cannot confirm a Recheck or Cancel authorization result, the panel disables both actions. Select Refresh activity to check for a later status from the same handoff. A new pending status restores the actions; a resolved status updates the panel. If no later status appears, the outcome remains uncertain and the actions stay disabled.
Folders, queued messages, and account preferences survive a reload for the same account. Studio clears them when you sign out or switch accounts. The device theme and palette remain.
Return to a chat
Generated chat titles appear in both the chat list and header when Studio receives a newer title, even if generation finishes after the run. A later operator rename takes precedence over an older generated title. If you return to the same open chat after its tab was hidden for at least 20 seconds, Studio refreshes its state and briefly reports only what it can verify, such as a run still working or the chat waiting for approval. Reloading the page does not create a return notice.
When a scheduled task delivers a start or completion note to this chat, the transcript shows the recorded note with its schedule and fire attribution. The note body appears as plain text. A task's fire history alone does not add a note to the chat. An open, visible chat checks saved history while idle, so a short delivery's reply and turns added from another client appear even when their live activity was missed.
Navigate and branch a conversation
In a long chat, use the numbered Message minimap beside the transcript to jump to a message. Its buttons work with a keyboard and remain available on a small screen. Select Scroll to latest message when you want to follow new messages again.
Select Reply in thread under a saved message to open a separate chat in the side panel. The thread copies the source chat's full history at the time you fork it, including messages after the one you selected. The selected message anchors the thread in the source transcript. Each occurrence has its own thread, even when two messages have the same text.
Select an image or tool result in the transcript to inspect its preview in the same side panel. Select Canvas in the chat header to edit Markdown notes and switch between Edit and Preview. Canvas notes stay in this browser for your account and the selected chat. Opening or previewing them does not send their text to Mecatl.
Inspect a session
Open Chat options > Inspect session to see its kind, state, model, usage, and available placement details. View transcript shows the saved messages in order and identifies an incomplete transcript. Inspect soul shows the resolved soul for your connection; it is shared across the sessions on that connection. Studio explains when any inspection view is unavailable.
The session list has a separate Inspect-only sessions group. Opening one shows Session details and, when permitted, its saved transcript. It has no chat composer or run controls. Copy session ID appears when permitted.
For a chat with eligible worktrees, select Choose worktree, choose a listed worktree, and confirm Fork in selected worktree or Clear in selected worktree. Studio opens the new session after the operation; the source chat keeps its placement. If the choice has become unavailable, Studio refreshes the list so you can choose again. Debug with AI first asks you to confirm that stored transcript and event evidence will be sent to the model in a separate diagnostic chat. The inspected session remains read-only to that debugger.
Try it locally with Compose
The repository's apps/docker-compose.yml starts a mecated container and
Studio on an internal network and publishes Studio on 127.0.0.1:3100.
-
Clone the repository and change into
apps/. -
Copy
.env.exampleto.envand set one provider key, for exampleANTHROPIC_API_KEY. -
Run:
docker compose up --build -
Open http://127.0.0.1:3100.
The local mecated runs without OIDC, so Studio starts in its unauthenticated
mode and every browser that reaches it acts as the same principal. The Compose
file opts into that with STUDIO_ALLOW_UNAUTHENTICATED=1 because it publishes
Studio on the loopback address only.
Deploy against mecak8s
In production Studio runs behind your ingress and connects to a mecak8s
deployment that publishes an OIDC profile (see
Cloud-native k8s with mecak8s). Users sign in through the
deployment's identity provider with Authorization Code and PKCE; Studio keeps
the session in encrypted, HttpOnly cookies and forwards the user's token on
every request.
Set these variables on the Studio container:
| Variable | Value |
|---|---|
MECATL_BASE_URL | The https:// address of the mecak8s gRPC listener, for example https://mecak8s.example.com. |
STUDIO_PUBLIC_URL | The origin your users open, for example https://studio.example.com. Studio derives the OIDC callback https://studio.example.com/api/v1/auth/callback and the Secure cookie flag from it. |
STUDIO_SESSION_SECRET | At least 32 bytes, shared by every Studio replica. Generate one with openssl rand -base64 48. |
STUDIO_TRUSTED_PROXY_HOPS | 1 when one ingress or load balancer sits in front of Studio, so rate limiting and audit records see the client address from X-Forwarded-For instead of the proxy. |
Register the callback URL with your identity provider's public client. Studio discovers the issuer, client ID, audience, and scopes from the deployment's protected-resource document and refuses to start when that document cannot be fetched, so a misconfigured target fails at deploy time.
MECATL_RESOURCE_URL is the deployment's canonical resource identifier: the
exact value its protected-resource document publishes as resource. Studio
fetches the document from that URL's /.well-known/oauth-protected-resource
path and refuses to start when the published resource differs from it. Set it
only when that identifier is not the gRPC address in MECATL_BASE_URL. The
local Compose file sets it because mecated serves gRPC and HTTP on separate
ports.
Studio answers only requests whose Host is the host of STUDIO_PUBLIC_URL, so
the ingress in front of it must pass the original Host header through.
Studio listens on port 3100 on every interface inside the image, answers
GET /api/health on any host name as soon as it starts, and runs as non-root
user 65532. It writes nothing to its own filesystem, so run it with a
read-only root filesystem, no Linux capabilities, and privilege escalation
disabled. In a Kubernetes pod, set this container securityContext:
securityContext:
runAsNonRoot: true
runAsUser: 65532
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
Scale Studio horizontally without shared storage: every replica needs only the
same STUDIO_SESSION_SECRET.
Sign in and recover
On your first visit, select Sign in to Mecatl in the centered card. If the card reports an outage, select Try again after the connection returns. When a session expires in an open workspace, use Sign in in the status banner. Studio keeps the original tab, route, and unfinished draft open while the popup completes login. If the browser blocks the popup or you close it, use Retry or the manual new-tab sign-in link. Return to the Studio tab after signing in through the new tab so it can check your session.
If your session expires while Studio is open, sign in again. Studio refetches reads after you return to the same account. Retry a write or stream yourself because the first request may have reached the deployment before its response was lost. If you sign in to a different account, Studio clears the previous account's browser data and unfinished draft before opening that workspace.
The connection banner shows a Studio or deployment outage ahead of a sign-in prompt. A pending connection check is neutral. When Studio cannot verify your session, use the banner's retry action; the current route and draft stay in place while it checks again.
Static token or no authentication
Studio can also run as a single service identity by setting MECATL_AUTH_TOKEN
together with MECATL_BASE_URL, or against a deployment that publishes no OIDC
profile. In both cases every browser that reaches Studio acts as one principal,
so the image refuses to start unless you also set
STUDIO_ALLOW_UNAUTHENTICATED=1. Use this only behind your own access control.
Configuration reference
| Variable | Default | Purpose |
|---|---|---|
MECATL_BASE_URL | unset | Connects to an existing Mecatl gRPC listener. The image requires it. |
MECATL_RESOURCE_URL | derived from MECATL_BASE_URL | Canonical RFC 9728 resource identifier. It must equal the resource the deployment publishes. |
MECATL_AUTH_TOKEN | unset | Static bearer token; disables browser login. |
STUDIO_PUBLIC_URL | unset | Browser-facing origin; required for browser login and inside the image. |
STUDIO_SESSION_SECRET | unset | Secret sealing the session cookies; required for browser login. |
STUDIO_PORT | 3100 | Listening port. |
STUDIO_HOST | 0.0.0.0 in the image, 127.0.0.1 elsewhere | Listening address. |
STUDIO_TRUSTED_PROXY_HOPS | 0 | Reverse-proxy hops to trust when reading X-Forwarded-For. |
STUDIO_RATE_LIMIT_MAX | 20 | Sign-in requests (login, callback, and logout) allowed per client within the window. The session check the browser makes on every page load is not counted. |
STUDIO_RATE_LIMIT_WINDOW_MS | 60000 | Rate-limit window in milliseconds. |
STUDIO_ACTIVITY_REPLAY_MAX | 2000 | Durable events one reattach replays before it tells the browser to read the transcript for older history. Live events are never bounded. |
STUDIO_ACTIVITY_MAX_STREAMS | 4 | Concurrent activity streams one chat admits per replica. |
STUDIO_ALLOW_UNAUTHENTICATED | unset | Set to 1 to run a static-token or no-authentication target inside the image. |
STUDIO_LOG_LEVEL | info | One of debug, info, warn, or error. Logs are JSON lines on standard error. |
For local development outside a container, the
Studio README
covers the spawn and mock runtime modes and the task studio:* commands.
Next steps
- Cloud-native k8s with mecak8s to deploy the server Studio connects to.
- Configure a deployment to enable the capabilities Studio shows.
- Permissions and posture to understand the approvals Studio surfaces in a chat.
Troubleshooting
Studio shows "Page not found" or "Something went wrong"
Use Go to Chats to return to the workspace. If a page reports an unexpected error, choose Try again first. If Studio cannot render its main interface, choose Reload Studio. Check the Studio URL if the page remains missing.
Studio exits with "authentication setup failed (auth_discovery_failed)"
Studio could not fetch the deployment's protected-resource document. Check that
MECATL_BASE_URL (or MECATL_RESOURCE_URL) points at the HTTPS address that
serves /.well-known/oauth-protected-resource. A deployment without OIDC
answers 404 there, which Studio treats as "no browser login" instead of an
error.
Studio exits with "authentication setup failed (auth_discovery_invalid)"
Studio fetched the protected-resource document but refused its contents. The
most common cause is a resource value that differs from MECATL_RESOURCE_URL,
or from MECATL_BASE_URL when MECATL_RESOURCE_URL is unset. Compare the
resource field the deployment publishes with the value Studio uses, including
scheme, port, and trailing path. The document must also name exactly one
authorization server over HTTPS, accept header bearer tokens, and publish the
Mecatl audience and client ID.
Every page answers "Host not allowed" (421)
The request reached Studio with a Host header other than the host of
STUDIO_PUBLIC_URL. Configure the ingress or load balancer to preserve the
original Host, and make sure users open exactly the STUDIO_PUBLIC_URL
origin. Without STUDIO_PUBLIC_URL, Studio answers only on localhost,
127.0.0.1, or [::1].
Sign-in loops back to the login screen
Studio sets Secure cookies when STUDIO_PUBLIC_URL is https://. Confirm
users open exactly that origin, that the identity provider redirects to
STUDIO_PUBLIC_URL/api/v1/auth/callback, and that every replica shares the same
STUDIO_SESSION_SECRET.