> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cycls.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Apps

> Return a FastAPI application and get a URL, sign-in, and per-user storage. Everything a function can do, an app can do too.

`@cycls.app` is a function that returns an ASGI application. Cycls serves it,
gives it a URL, and wires in identity and storage.

```python notes.py theme={null}
import cycls


@cycls.app(auth=cycls.Clerk(), volumes={"/workspace": cycls.Volume("notes")})
def notes():
    from fastapi import FastAPI

    app = FastAPI()

    @app.get("/me")
    async def me(user=notes.auth):
        return {"id": user.id, "plan": user.plan}

    @app.post("/notes")
    async def create(body: dict, ws=notes.workspace):
        db = cycls.DB(ws)
        await db.put(f"docs/{body['id']}", body)
        return body

    return app
```

```bash theme={null}
cycls run notes.py             # localhost:8080, reload on save
cycls run notes.py --remote    # https://dev-notes.cycls.ai, hot swap on save
cycls deploy notes.py          # https://notes.cycls.ai
```

## The decorator

```python theme={null}
@cycls.app(name=None, image=None, volumes=None, auth=None, memory="1Gi")
```

| Argument  | Meaning                                                              |
| --------- | -------------------------------------------------------------------- |
| `name`    | deployment name and subdomain, defaults to the function name         |
| `image`   | [`cycls.Image`](/build/images) for packages and bundled files        |
| `volumes` | mount paths to [volumes](/build/volumes)                             |
| `auth`    | `cycls.Clerk()` or `cycls.JWT(...)`, see [Authentication](/web/auth) |
| `memory`  | container memory                                                     |

The body runs inside the container at startup. Import heavy dependencies there,
not at module level, so your local process stays light.

## Identity

With `auth=` set, two dependencies become available on the decorated object.

```python theme={null}
@app.get("/me")
async def me(user=notes.auth):          # authenticated User, or 401
    return user


@app.get("/files")
async def files(ws=notes.workspace):    # that user's storage scope
    return [p.name for p in ws.root.iterdir()]
```

`user` carries `id`, `org_id`, `org_slug`, `org_role`, `org_permissions`, `plan`
and `features`. See [Authentication](/web/auth) for providers and claims.

## Storage

`notes.workspace` resolves a per-user scope on the volume mounted at
`/workspace`. Two things live there:

<Tabs>
  <Tab title="Key-value">
    ```python theme={null}
    @app.post("/notes")
    async def create(body: NoteIn, ws=notes.workspace):
        db = cycls.DB(ws)
        await db.put(f"docs/{uuid4().hex[:12]}", body.dict())
        return {"ok": True}


    @app.get("/notes")
    async def list_notes(ws=notes.workspace):
        return [d async for _, d in cycls.DB(ws).items(prefix="docs/")]
    ```

    Atomic per key, prefix scans, JSON values. Backed by the volume, so it survives
    redeploys.
  </Tab>

  <Tab title="Files">
    ```python theme={null}
    @app.get("/report")
    async def report(ws=notes.workspace):
        path = ws.root / "report.csv"
        return path.read_text()
    ```

    `ws.root` is a `Path` scoped to that user. Write files there and they persist
    exactly like any other volume content.
  </Tab>
</Tabs>

<Warning>
  Both need a volume at `/workspace`. Without one the error arrives on first use,
  not at deploy, so declare it whenever the app touches storage.
</Warning>

## Serving a front end

Bundle the HTML with the image and return it from a route.

```python theme={null}
from pathlib import Path

HTML = str(Path(__file__).parent / "index.html")


@cycls.app(image=cycls.Image().copy(HTML, "index.html"), auth=cycls.Clerk())
def dashboard():
    from fastapi import FastAPI
    from fastapi.responses import HTMLResponse

    app = FastAPI()

    @app.get("/")
    async def index():
        pk = dashboard._auth_provider.resolve(dashboard.prod).get("pk", "")
        return HTMLResponse(Path("index.html").read_text().replace("__CLERK_PK__", pk))

    return app
```

The publishable key is resolved at request time, so the same file works locally
and in production without a build step.

## Running untrusted commands

`cycls.Sandbox` is a fluent wrapper around `bubblewrap` for executing commands
with a read-only root, a cleared environment and no network unless you ask.

```python theme={null}
sandbox = (
    cycls.Sandbox()
    .setenv(PATH="/usr/local/bin:/usr/bin:/bin")
    .network(False)
    .timeout(120)
)


@app.post("/run")
async def run(cmd: str, ws=terminal.workspace):
    result = await sandbox.bind(str(ws.root), "/workspace").chdir("/workspace").run(
        ["bash", "-lc", cmd]
    )
    return {"output": result.output, "code": result.code}
```

| Method                       | Effect                                                     |
| ---------------------------- | ---------------------------------------------------------- |
| `.bind(src, dst)`            | read-write bind mount                                      |
| `.ro_bind(src, dst)`         | read-only bind mount                                       |
| `.setenv(**vars)`            | set environment variables                                  |
| `.chdir(path)`               | working directory                                          |
| `.network(on)`               | allow network, off by default                              |
| `.timeout(seconds)`          | kill after this long                                       |
| `await .run(argv, env=None)` | returns `stdout`, `stderr`, `code`, `timed_out`, `.output` |

## Programmatic control

```python theme={null}
notes.local()              # Docker with hot reload
notes.local(watch=False)   # Docker, no watcher
notes.deploy()             # production
```

For `python notes.py` to serve, guard it the usual way:

```python theme={null}
if __name__ == "__main__":
    notes.local()
```

## Next

<Card title="Agents" icon="comments" href="/agents/overview">
  The chat layer on top of apps: a managed model loop, tools and a web UI.
</Card>
