# ADR-0018: Studio Distribution and Local Access

**Status**: Accepted (decision); **Implementation deferred** — this PR does
not ship the packaged Studio wheel, the `li studio` no-arg launcher, or the
`StaticFiles` mount described below. The frontend remains a separate Next.js
dev app on port 3000 and `apps/studio/server/app.py` only exposes API
routers + `/health`. The pieces specified here (move/package server under
`lionagi/studio/`, bundle built static assets in the wheel, mount static
after `/api/*`, make `li studio` start the server) are tracked as
follow-up work and are NOT a hard contract for this PR.

**Date**: 2026-05-20

## Context

Lion Studio is a web UI (React frontend + Python/FastAPI backend) that needs
to reach the user's local filesystem, CLI tools (`li agent`, `li play`, etc.),
and `~/.lionagi/state.db`. Users need a way to install and run Studio without
frontend toolchain knowledge (no `npm install`, no Node.js requirement).

Docker is the obvious distribution mechanism for web apps. But Studio is not a
typical web app — it is a **local dev tool** whose entire value depends on
unrestricted host access:

- The backend reads/writes `~/.lionagi/` (state.db, runs/, agents/, playbooks/).
- The backend spawns CLI subprocesses (`li agent`, `li play`, `li o flow`).
- SSE streams depend on subprocess stdout/stderr piping.
- The frontend serves on `127.0.0.1` only (ADR-0008 security posture).

Docker Compose would require volume-mounting `~/.lionagi/`, bind-mounting the
host's `$PATH` or socket-forwarding for subprocess spawning, and exposing the
container's port. This is more complex than running the server natively, and
the isolation Docker provides is actively unwanted — Studio *is* the local
environment's UI.

## Decision

### Primary distribution: bundled static assets in the Python package

The React frontend is built at CI/release time. The built static files
are included in the Python package under `lionagi/studio/static/`. The
FastAPI server serves them directly.

```text
pip install lionagi[studio]    # or: pip install lionagi (if studio becomes default)
li studio                      # starts uvicorn, serves frontend + API on 127.0.0.1:8765
```

Users need: Python 3.11+, `pip install lionagi[studio]`. No Node.js, no npm,
no Docker. One command to start.

**Next.js output strategy — `output: "export"` (static HTML/JS/CSS)**:

Next.js builds do not output to `dist/`. The output directory depends on
configuration:

- Default (no config): `.next/` — server-rendered output; requires Node.js at runtime.
- `output: "standalone"`: `.next/standalone/` + `.next/static/` — self-contained Node server.
- `output: "export"`: `out/` — fully static HTML/JS/CSS; no Node.js at runtime.

For the bundled-static-assets-in-pip-wheel model, `output: "export"` is the
correct choice. It produces a `out/` directory of static files that FastAPI
can serve with `StaticFiles`, and has no Node.js runtime requirement for
end users. This requires adding `output: "export"` to `apps/studio/frontend/next.config.mjs`
when this ADR is implemented:

```js
// apps/studio/frontend/next.config.mjs  (to add at implementation time)
const nextConfig = {
  output: "export",
};
export default nextConfig;
```

> **Caveat**: `output: "export"` disables App Router server components, API
> routes, and `next/image` optimization. Studio currently uses none of these —
> all API calls go to the Python backend on `:8765`. If server components or
> Next.js API routes are added in the future, reconsider `"standalone"` output
> and update this ADR.

**Build pipeline** (CI only — users never run this):

```bash
# ADR-0002 §Appendix A: --legacy-peer-deps is required due to ESLint 9 /
# @eslint/js peer conflict introduced by the Next.js 14 → 16 upgrade.
# Every npm install in CI MUST use this flag until the conflict is resolved.
cd apps/studio/frontend && npm ci --legacy-peer-deps && npm run build
cp -r out/ ../../lionagi/studio/static/
```

The `lionagi/studio/static/` directory is `.gitignore`d (built artifact, not
source). The Python package's `pyproject.toml` includes it via
`[tool.hatch.build.targets.wheel] / packages`.

> **ADR-0019 candidate**: when the `apps/studio/` tree is relocated to
> `lionagi/studio/` (as part of packaging), the build pipeline source path and
> the `StaticFiles` mount path will change together. A separate ADR should
> capture the relocation decision, target layout, and any changes to the
> `output: "export"` static serving path.

**Server startup** (`li studio`):

```python
from fastapi.staticfiles import StaticFiles

app.mount("/", StaticFiles(directory=STATIC_DIR, html=True), name="studio")
```

API routes (`/api/*`) are registered before the static mount so they take
precedence. The frontend's client-side router handles all non-API paths.

### Development mode

Developers who modify the frontend use the standard dev server:

```bash
cd apps/studio/frontend && npm run dev    # Next.js dev server on :3000
cd apps/studio && uv run python -m server  # API server on :8765
```

The frontend is Next.js (not Vite — there is no `vite.config.ts`). In development, the
Next.js dev server runs on port 3000 and reads the `NEXT_PUBLIC_STUDIO_API_BASE` environment
variable to resolve API calls:

```bash
NEXT_PUBLIC_STUDIO_API_BASE=http://localhost:8765 npm run dev
```

When `NEXT_PUBLIC_STUDIO_API_BASE` is set, `lib/api.ts` prefixes all `/api/*` fetch calls
with that base URL, routing them to the backend on `:8765`. No proxy configuration in
`next.config.mjs` is required for this workflow.

### Optional: Docker Compose for server/team deployment

For a shared Studio instance (e.g., a team server monitoring a shared
`~/.lionagi/` directory on a build machine), Docker Compose is available
but explicitly **not the primary path**:

```yaml
# docker-compose.yml (optional, not shipped in pip package)
services:
  studio:
    build: .
    volumes:
      - ${LIONAGI_HOME:-~/.lionagi}:/home/lion/.lionagi
      - /var/run/docker.sock:/var/run/docker.sock  # if CLI needs Docker
    ports:
      - "127.0.0.1:8765:8765"
    environment:
      - LIONAGI_HOME=/home/lion/.lionagi
```

**Limitations of Docker mode** (document clearly):

- CLI subprocesses run inside the container, not on the host. Tools available
  inside the container may differ from the host.
- Git operations see the container's git config, not the host's.
- SSH keys, API tokens, and shell profiles are not available unless explicitly
  mounted.
- Performance overhead from volume mounts on macOS (osxfs/virtiofs).

Docker mode is a **known-limited deployment option**, not the recommended path.
The README should say: "For local use, `pip install` is simpler and has full
host access. Docker is for shared/server deployments."

### `li studio` extras dependency

The `[studio]` extra adds:

- `uvicorn` — ASGI server
- `fastapi` — already a core dependency of lionagi
- `aiosqlite>=0.21.0` — required by studio services (sessions, shows, runs, definitions)
  that query `state.db` directly; also a core dependency, but listed explicitly in `[studio]`
  so that `pip install lionagi[studio]` is self-contained without relying on transitive
  resolution from the base package

No heavyweight additions. The frontend is pre-built static HTML/JS/CSS —
zero runtime frontend dependencies.

### Future: Desktop wrapper (deferred)

Tauri or Electron could wrap Studio as a native app with system tray, auto-start,
and OS-level notifications. This is deferred until Studio is feature-complete.
The bundled-static-assets architecture is compatible — a desktop wrapper would
embed the same uvicorn server and open a webview to `127.0.0.1:8765`.

## Consequences

**Positive**

- One-command install and start (`pip install lionagi[studio] && li studio`).
- Full host access — no Docker isolation getting in the way of local dev tooling.
- No frontend toolchain required for users (Node.js, npm stay in CI only).
- Docker option exists for team/server deployments without blocking the primary path.
- Compatible with future desktop wrapper without architecture changes.

**Negative**

- CI must build the frontend and include it in the wheel. Adds a build step.
- `lionagi[studio]` wheel is larger (~2-5 MB for built React assets).
- Docker mode has documented limitations around host access — users who expect
  full Docker + full host access will be disappointed (this is a fundamental
  Docker constraint, not a lionagi limitation).

## Alternatives Considered

| Alternative | Why Rejected |
|-------------|-------------|
| Docker-only distribution | Walls off local filesystem, CLI tools, SSH keys. The primary use case (local dev tool) becomes the hardest path. |
| npm-based install (users run `npm install`) | Adds Node.js as a user requirement. lionagi is a Python tool; requiring a second ecosystem for the UI is friction. |
| Electron desktop app (primary) | Heavy (~100MB+), slow to build, Chromium bundled. Premature for a tool with one primary user. Revisit post-feature-complete. |
| Separate PyPI package (`lionagi-studio`) | Splits the install. Users must coordinate versions. The studio is part of lionagi, not a separate product. |
| Serve frontend from CDN | Requires internet. Studio must work offline (localhost-only, air-gapped environments). |

## References

- [ADR-0008](ADR-0008-studio-v1-scope.md) — Security posture (127.0.0.1 only)
- [ADR-0013](ADR-0013-zero-dependency-ui.md) — Zero-dependency frontend (no component library)
- [ADR-0014](ADR-0014-cli-primary-studio-secondary.md) — CLI-primary, Studio-secondary
