Work with Marina applets

SkillDatabases & data

Lets your agent build, publish, update, and roll back small web apps that run behind Marina authentication.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Work with Marina applets skill

About this capability

Build, validate, publish, update, inspect, query, roll back, or archive a dynamic Marina applet. Use for Artifact+-style web apps that should run behind Marina authentication, carry small bundled assets or data, use an applet-owned Postgres schema, or add a trusted Python ASGI backend without a Mari

What this skill tells your AI

The instructions your AI receives, as published by marin-community/marin in .agents/skills/marina-applet/SKILL.md and read by ahel’s review.

Read the Publishing an applet section of infra/marina/README.md before changing an applet or publishing one. Use infra/marina/examples/problem-set-applet/ as the minimal working package and backend reference. Treat those files and marina --help as authoritative when this skill disagrees with the checkout.

Choose the applet shape

Ensure the built package contains:

my-applet/
  applet.toml
  dist/index.html

Add browser assets below dist/. Use only relative asset, API, and query URLs so both /a/<uuid>/ and /a/<uuid>/v/<revision>/ work.

The source directory may omit dist/index.html only when applet.toml has a build_command that creates it before packaging.

Use plain HTML and JavaScript for a small single-view applet. Use Vue with Vite when the applet needs multiple views, reusable components, or substantial client state. For Vue applets:

  • Set Vite's base to "./" so built asset URLs remain relative.
  • Use createWebHashHistory() when the app has client-side routes. This avoids hard-coding an applet UUID or revision into the router base.
  • Use relative backend calls such as fetch("api/results") and fetch("query", ...); do not start applet-owned URLs with /.
  • Bundle dependencies into dist/. Marina's content security policy does not allow scripts loaded from a third-party CDN.

Keep the source index.html, package.json, the Vite configuration, and src/ beside applet.toml. They are local build inputs and are not uploaded. A typical manifest uses build_command = "npm ci && npm run build". Marina runs that command before every publish by default. Use --no-build only when the existing dist/ has already been built and validated.

Use the applet's implicit Postgres schema for mutable or queryable data. Do not create or attach a separate database. Load simple scalar tables with the CLI, or add server/ when the applet needs parameterized business logic, custom responses, or server-side integration.

An optional backend has this shape:

my-applet/
  server/__init__.py
  server/app.py

Declare it in applet.toml:

title = "Problem sets"
description = "Browse generated math problem sets."
python_entrypoint = "server.app:create_api"

Export create_api(services) -> ASGIApp. Use services.engine() for an engine configured with the applet role and schema. Optionally export migrate(connection) -> None; it runs inside the publish transaction before the new revision becomes current. Keep schema changes compatible with retained revisions.

Use get_verified_identity() from rigging.server_auth when a backend needs the authenticated caller. Import only packages already declared in infra/marina/pyproject.toml. Shared Marin helpers must come from installed packages under lib/*; do not import checked-in apps under infra/marina/apps.

Treat Python applets as trusted plugins. They run inside Marina with its filesystem, network, credentials, and process identity. Do not publish code from an untrusted source or depend on lifespan events, background workers, or local filesystem persistence.

Build and validate

Inspect existing Marina and repository helpers before introducing a framework or dependency. Build frontend output locally; Marina does not install applet dependencies. marina publish runs a declared build_command before packaging by default, and the command may create dist/index.html. Pass --no-build to reuse an existing validated dist/.

Validate the package and import a declared backend from the current Marina environment without changing server state:

uv run marina validate my-applet

The report names its completed checks and omissions. It does not execute the backend factory, run its migration, connect to Postgres, or start a browser. Use package-only validation when only the upload archive matters:

uv run marina publish my-applet --dry-run

Dry-run reports backend imports as unchecked. Both commands reject executable inline scripts under Marina's script-src 'self' policy. Load JavaScript from relative packaged files.

Respect the current package limits: 25 MiB total, 8 MiB per file, and 2,000 regular files. Keep large data out of the package until Marina gains an object store path.

Run the complete local stack with:

uv run marina publish my-applet --local

This starts disposable Postgres in Docker, installs Marina's privileged applet provisioning, migrates the registry, starts Marina on a free loopback port, publishes the applet with its migration, and prints its immutable revision URL. It requires a running Docker daemon, binds only to loopback, and does not enable IAP authentication. It stays in the foreground. After validation, send Ctrl-C and verify that both Marina and the container stop. If uv cannot write its cache in a restricted checkout, use the current checkout's .venv/bin/marina executable instead of installing or synchronizing dependencies.

To preserve the disposable Postgres stack across edits, leave the first publish --local --json process running. From a second terminal, publish the edit with this command, using each returned revision as the next base version:

uv run marina publish my-applet --url <printed-origin> \
  --update <printed-id> --base-version <current-version>

Open the printed immutable revision URL. Exercise the frontend, every backend route, caller identity when used, and at least one schema read/write path. For Marina code changes, run the focused tests and then:

cd infra/marina && uv run pytest

Use applet data

The applet role owns applet_<uuid-without-hyphens> and inherits read access to checked-in and other applet schemas. It can write only its own schema. Prefer the CLI for small datasets:

uv run marina applets table load <uuid> observations rows.parquet --replace
uv run marina applets sql <uuid> 'SELECT * FROM observations'

Table loading accepts JSON arrays, JSONL, CSV, and Parquet with scalar columns. Browser code may post one SQLAlchemy text statement to relative URL query. Query results are limited in Postgres to 10,000 rows and 5 MiB with a 10-second timeout. Transaction and role-control commands, data-modifying WITH, and mutation statements with RETURNING are rejected.

Prefer a Python backend over browser-submitted SQL when inputs need validation, authorization, stable response types, or multiple database operations.

Publish and operate

Treat a request to build or update a Marina applet as authorization to publish it and perform the server writes needed for the requested behavior, including schema migrations and requested data loading. Validate the package, then publish without asking for separate confirmation. Honor requests for local-only work, drafts, or review before publishing. Rollback, archive, and unrelated data mutations still need authorization within the user's request. The default target is https://marina.oa.dev, and the CLI uses cached iris login credentials or ambient Google credentials through IAP.

First publish:

uv run marina publish my-applet --json

Record the returned UUID, revision, and immutable revision URL. A successful publish runs its migration and atomically selects the new revision as current. Fetch the stable current URL with:

uv run marina applets versions <uuid> --json

Update the same applet with optimistic concurrency:

uv run marina publish my-applet --update <uuid> --base-version <current>

Applets are private by default. Use --mode public on a publish or update when the frontend and its GET backend routes may be disclosed without authentication. An update without --mode preserves the current mode. Change only the mode with marina applets mode <uuid> public|private.

Public mode is limited to GET and HEAD requests, but Marina cannot prove that a GET backend is side-effect-free. Use it only for trusted applet code and data that may be disclosed to the unrestricted internet. Do not use public mode for untrusted publishers, sensitive data, SQL query access, or mutation routes.

Inspect before mutating lifecycle state:

uv run marina applets list
uv run marina applets versions <uuid>

Roll back only to a retained revision:

uv run marina applets rollback <uuid> <revision>

Archive an applet with:

uv run marina applets archive <uuid>

Archival hides the applet but preserves its schema and retained revisions. Marina retains the current revision plus four other recent revisions.

Report the applet UUID, stable URL, immutable revision URL, validation or test results, and any trusted-code or schema-compatibility caveat that affects use.

Signals

GitHub stars
4k
Forks
303
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
marina-applet
Source
github.com/marin-community/marin