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

# Functions

> Turn any Python function into a container that runs on your laptop, in the cloud, or on a schedule, without writing a Dockerfile.

`@cycls.function` is the bottom layer. It takes a Python function, packages it
with the dependencies you declare, and runs it wherever you point it.

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


@cycls.function(image=cycls.Image().pip("numpy"))
def simulate(n=1_000_000):
    import numpy as np
    pts = np.random.rand(int(n), 2)
    return float(4 * ((pts ** 2).sum(axis=1) <= 1).mean())
```

Three verbs decide where it runs:

```python theme={null}
simulate.run(1000)        # your Docker
simulate.remote(1000)     # Cycls Cloud, the code you are holding
simulate.deploy()         # Cycls Cloud, frozen and callable by name
```

## How the code gets there

The function is serialized with cloudpickle, bytecode, closures and captured
variables included, then executed inside a container built from your `Image`.
The same pickle runs locally and in the cloud, so what you tested is what ships.

Two version rules keep that safe, and both are enforced for you:

* The container's Python matches your host's major and minor version.
* The container's cloudpickle matches your host's exact version.

<Warning>
  Functions defined in the file you deploy travel by value. Functions imported from
  another module travel by reference, so the container needs that module. Bundle it
  with `cycls.Image().copy("helpers.py")`, and anything copied lands in `/app`,
  which is on `sys.path`.
</Warning>

## Running locally

```python theme={null}
result = simulate.run(1000)
```

The image is built or reused, the function executes, and the return value comes
back. Print statements stream to your terminal while it runs.

To look around inside the exact environment the function sees:

```bash theme={null}
cycls shell simulate.py
# Entering cycls/simulate:730f149a (exit to leave)
root@a1b2c3:/app#
```

## The development loop

```bash theme={null}
cycls run simulate.py                # rerun on every save, local Docker
cycls run simulate.py --remote       # rerun on every save, in the cloud
cycls run simulate.py --n 1000       # arguments bind to the signature
```

Trailing `--name value` arguments bind to the function's parameters. Annotated
parameters convert through their annotation, so `n: int` gets an int.
Unannotated ones are parsed as Python literals, so `--data "[1,2]"` becomes a
list. Anything else stays a string.

A save during a run queues the next run instead of killing the current one.

For anything with several calls, a fan-out, or a mix of local and remote, mark a
driver:

```python theme={null}
@cycls.local_entrypoint
def main(n: int = 1_000_000):
    print(simulate.remote(n))
    print(simulate.map([10, 20, 30]))
```

The entrypoint runs on your machine on every save, and the verbs inside it decide
where the work happens. Keep driver calls inside it. A top-level
`simulate.remote(...)` fires on every import, including during `cycls deploy`.

## Running in the cloud with current code

```python theme={null}
simulate.remote(1_000_000)          # one call
simulate.map([10**6] * 100)         # one call per item, ordered results
```

`remote()` ships the live bytecode to an executor, which is a small service
provisioned once per image and shared by every function using that image. The
first call for a given image takes about ninety seconds while it provisions.
After that, a call costs network plus compute. Edit the function, call again, and
the new code runs. There is no redeploy step.

`map()` fans one call per item across autoscaled instances and returns results in
input order. It raises on the first failure, so return errors as data when you
want per-item tolerance:

```python theme={null}
@cycls.function(image=cycls.Image().pip("httpx", "beautifulsoup4"))
def scrape(url):
    try:
        ...
    except Exception as e:
        return {"url": url, "error": str(e)}
```

## Deploying

```bash theme={null}
cycls deploy simulate.py
# Deployed: https://simulate.cycls.ai
# Call it: cycls.remote("simulate")(...)
```

Deploy reads the function's shape. A function that takes a `port` parameter is a
server and gets its own URL. A bare function becomes a named endpoint, frozen at
deploy time and callable from any machine that has your API key:

```python theme={null}
import cycls

pi = cycls.remote("simulate")(10_000_000)
results = cycls.remote("simulate").map([10**6] * 100)
```

The difference matters. `f.remote()` runs your current code, which is what you
want while developing. `cycls.remote("name")` calls what was deployed, which is
what a caller without your source needs.

## Serving instead of returning

Any function that binds a port is a server:

```python theme={null}
@cycls.function(image=cycls.Image().pip("fastapi", "uvicorn"))
def api(port):
    from fastapi import FastAPI
    import uvicorn

    app = FastAPI()

    @app.get("/")
    async def root():
        return {"ok": True}

    uvicorn.run(app, host="0.0.0.0", port=port)
```

```bash theme={null}
cycls run api.py        # http://localhost:8080
cycls deploy api.py     # https://api.cycls.ai
```

For a service with auth and per-user storage, use [`@cycls.app`](/build/apps),
which is this pattern with the plumbing already done.

## Sizing and limits

```python theme={null}
@cycls.function(
    image=cycls.Image().pip("torch"),
    cpu=4,
    memory="8Gi",
    timeout=1800,
    concurrency=1,
)
def train(epochs): ...
```

| Argument         | Meaning                                                       |
| ---------------- | ------------------------------------------------------------- |
| `cpu`            | 1, 2, 4, 6 or 8                                               |
| `memory`         | `"512Mi"` through `"32Gi"`                                    |
| `timeout`        | seconds a single request may run, up to 3600                  |
| `concurrency`    | requests one instance serves at once. Set it to 1 for compute |
| `volumes`        | mount paths to [volumes](/build/volumes)                      |
| `schedule`       | a [cron](/build/cron) that fires the deployment               |
| `python_version` | pin the container's Python, must match your host              |

## Keeping state warm

The process lives across calls on an instance, so expensive setup can be paid
once per instance using a mutable default:

```python theme={null}
@cycls.function(image=cycls.Image().pip("fastembed"))
def embed(texts, _model={}):
    if "m" not in _model:
        from fastembed import TextEmbedding
        _model["m"] = TextEmbedding("BAAI/bge-small-en-v1.5")
    return [v.tolist() for v in _model["m"].embed(list(texts))]
```

First call loads the model, every call after is warm. That is a self-hosted
embedding API in ten lines.

## Method reference

| Method                     | Meaning                                             |
| -------------------------- | --------------------------------------------------- |
| `.run(*args, **kwargs)`    | execute in local Docker, `port=` serves             |
| `.remote(*args, **kwargs)` | execute current code in the cloud                   |
| `.map(items, workers=16)`  | fan current code across instances, ordered          |
| `.watch(*args, **kwargs)`  | run with file watching                              |
| `.build()`                 | build the image without running                     |
| `.deploy()`                | freeze and publish                                  |
| `cycls.remote(name)`       | a callable for a deployed endpoint, `.map` included |

## Next

<Card title="Images" icon="layer-group" href="/build/images">
  Declare packages, system libraries and bundled files.
</Card>
