Skip to main content

Distribute Intelligence

Website | Docs | Cycls Cloud | Functions | Volumes | CLI

cycls Python package on PyPi Tests Cycls newsletter Cycls Twitter


Cycls

The deep-stack AI SDK for Python. Every layer of an AI agent (runtime, interface, intelligence, state) as a composable Python primitive, in one file, deployed with one command.

Agent extends App (chat product + managed LLM loop)
    └── App extends Function (blocking ASGI service)
        └── Function (Docker containerization)

Distribute Intelligence

Write an agent. Four primitives compose it. Deploy it with one command.

import cycls

image = cycls.Image().copy(".providers.env", ".env")

chats = cycls.Volume("my-agent")

web = (
    cycls.Web()
    .auth(cycls.Clerk())
    .title("My Agent")
)

llm = (
    cycls.LLM()
    .model("anthropic/claude-sonnet-4-6")
    .system("You are a helpful assistant.")
    .allowed_tools(["Bash", "Editor", "WebSearch", "Canvas"])
)


@cycls.agent(image=image, web=web, volumes={"/workspace": chats})
async def my_agent(context):
    async for ev in llm.run(context=context):
        yield ev
cycls deploy my_agent.py   # live at https://my-agent.cycls.ai

That deploy gives you a chat interface, sign-in, per-user files and chats, share links, an OpenAI-compatible API endpoint, and a sandboxed tool loop.

Installation

pip install cycls

Get an API key at cloud.cycls.com. Full documentation at docs.cycls.com.

The Primitives

Four composable builders, three decorators, one CLI.

Primitives (declare once, reuse anywhere):
  cycls.Image    container build config (pip, apt, copy, run commands)
  cycls.Web      UI, auth, branding, analytics, workspaces
  cycls.LLM      model, system prompt, tools, budgets, reasoning
  cycls.Volume   named persistent storage, attached by mount path

Decorators (compose primitives into deployable units):
  @cycls.function(image=, volumes=, schedule=)   containerized compute
  @cycls.app(image=, volumes=, auth=)            blocking ASGI service
  @cycls.agent(image=, web=, volumes=)           managed chat product

CLI:
  cycls init [name]             scaffold a starter agent
  cycls run file.py             local Docker with hot-reload
  cycls run file.py --remote    cloud dev loop, no Docker needed
  cycls deploy file.py          production deploy
  cycls ls                      list deployments
  cycls logs <name> -f          tail logs
  cycls cost <name>             aggregate model spend
  cycls sql [QUERY]             SQL over logs and billing
  cycls volume ls               manage persistent storage
  cycls rm <name>               delete a deployment

Every primitive is a fluent immutable builder. Every decorator accepts exactly those primitives, never grab-bag kwargs.

Running

my_agent.local()             # local Docker + hot-reload (localhost:8080)
my_agent.local(watch=False)  # local Docker, no watch
my_agent.deploy()            # production: https://my-agent.cycls.ai

Or via the CLI (recommended):

cycls run my_agent.py           # local Docker + hot-reload
cycls run my_agent.py --remote  # cloud dev URL, hot-swap on save
cycls deploy my_agent.py        # production

run is local, --remote is cloud, deploy freezes. The same three words work in Python and on the command line. See docs/function.md.

State

Agents keep chats, files and credentials on a volume mounted at /workspace, so the decorator requires one:

@cycls.agent(volumes={"/workspace": cycls.Volume("my-agent")})
async def my_agent(context):
    ...

Volumes are named storage, created on first reference, shared by name across deployments, and alive until you delete them explicitly. cycls rm detaches volumes and never touches their data. Full story: docs/volume.md.

Multi-provider LLM

One adapter covers Anthropic natively and every OpenAI-compatible endpoint through provider/model strings and a base URL:

cycls.LLM().model("anthropic/claude-sonnet-4-6")     # Anthropic, native API
cycls.LLM().model("openai/gpt-5.4")                  # OpenAI

# Open weight models
cycls.LLM().model("deepseek/deepseek-flash").base_url("https://api.deepseek.com/v1")
cycls.LLM().model("moonshotai/kimi-k3").base_url("https://api.moonshot.ai/v1")
cycls.LLM().model("zai/glm-5.3").base_url("https://open.bigmodel.cn/api/paas/v4")

# Your own inference server
cycls.LLM().model("local/kimi-k3").base_url("http://localhost:8000/v1")

Reasoning control, tool calls and streaming are unified across providers. .thinking("low"|"medium"|"high"|"adaptive"|None) is translated into each vendor's dialect, and .extra_body() is the escape hatch for anything unmapped.

Tools

Built-in tools are enabled by name. Each brings its own prompt guidance.

llm = cycls.LLM().allowed_tools([
    "Bash",       # shell in a bubblewrap sandbox rooted at the workspace
    "Editor",     # read and edit workspace files
    "WebSearch",  # portable search + fetch, or provider-native
    "Browser",    # a real Chrome session, when a browser service is configured
    "DataBase",   # per-user key-value store
    "Canvas",     # open a finished file in the side panel
    "Apps",       # bundle a source folder into an installable mini app
    "Suggest",    # one follow-up chip above the composer
    "Ask",        # up to three questions on one card
])

Custom tools are bare JSON schemas with async handlers registered via .on(name, handler). The handler's return value flows to both the UI stream and the model's tool_result.

TOOLS = [
    {
        "name": "render_image",
        "description": "Display an image to the user.",
        "inputSchema": {
            "type": "object",
            "properties": {"src": {"type": "string"}},
            "required": ["src"],
        },
    }
]


async def render_image(args):
    return {"type": "image", "src": args["src"]}


llm = cycls.LLM().tools(TOOLS).on("render_image", render_image, label=lambda i: i["src"])

A handler can declare a second parameter to receive a ToolContext with the user, workspace and chat id.

Connectors and MCP

A connector is a grant a person makes once, for themselves or their team. Declare it on both builders and the loop attaches the credential server-side when a tool runs.

notion = cycls.OAuth2(
    "notion",
    authorize="https://api.notion.com/v1/oauth/authorize",
    token="https://api.notion.com/v1/oauth/token",
    client_id=cycls.env("NOTION_CLIENT_ID"),
    secret=cycls.env("NOTION_CLIENT_SECRET"),
    scopes=["read_content"],
    api="https://api.notion.com",
    scope="either",
)

web = cycls.Web().auth(cycls.Clerk()).connectors(notion)
llm = cycls.LLM().connectors(notion)

cycls.Key takes a pasted key, cycls.Endpoint takes a private URL that is itself the credential. cycls.MCP connects remote MCP servers, and since the harness speaks the protocol itself, MCP works on every provider.

Every tool call is classified as read, write or destructive. Reads always run, writes follow the composer's Auto switch, destructive calls always ask, and an approval binds to the exact arguments shown on the card.

Streaming Components

Yield structured objects from an agent body for rich streaming responses:

@cycls.agent(web=web, volumes={"/workspace": chats})
async def demo(context):
    yield {"type": "thinking", "thinking": "Analyzing the request..."}
    yield "Here's what I found:\n\n"

    yield {"type": "table", "headers": ["Name", "Status"]}
    yield {"type": "table", "row": ["Server 1", "Online"]}
    yield {"type": "table", "row": ["Server 2", "Offline"]}

    yield {"type": "code", "code": "result = analyze(data)", "language": "python"}
    yield {"type": "callout", "callout": "Analysis complete!", "style": "success"}
Component Required keys
{"type": "text", "text": "..."} a bare string does the same
{"type": "thinking", "thinking": "..."} accumulates in a bubble
{"type": "code", "code": "...", "language": "..."} accumulates
{"type": "table", "headers": [...]} / {"type": "table", "row": [...]} row by row
{"type": "status", "status": "..."} replaces the previous status
{"type": "callout", "callout": "...", "style": "info|warning|error|success"} one card
{"type": "image", "src": "...", "alt": "...", "caption": "..."} one image
{"type": "sources", "sources": [{"title", "url", "snippet"}]} citation chips
{"type": "ui", "action": "open_plan_modal"} fire-and-forget client action

Provider reasoning deltas (Claude extended thinking, OpenAI delta.reasoning) map to the thinking channel automatically, so llm.run() produces thinking bubbles with no extra work.

llm.run() yields these same dicts, so the body passes them through with yield ev. cycls.to_ui(ev) still works and is now an identity function kept for older code.

Context Object

@cycls.agent(web=web, volumes={"/workspace": chats})
async def chat(context):
    context.messages          # [{"role": "user", "content": "..."}]
    context.messages.raw      # full data including UI component parts
    context.last_message      # text of the most recent message
    context.user              # User(id, org_id, plan, features, ...) when auth is set
    context.chat_id           # current chat
    context.workspace         # this user's storage scope
    context.prod              # True via deploy, False via run
    context.disabled_tools    # tools the person switched off in Settings

Authentication

Auth providers are first-class objects. cycls.Clerk() uses Cycls's hosted Clerk by default; cycls.JWT(...) covers any OIDC provider (Auth0, WorkOS, Supabase, Okta, Firebase).

# Cycls's default Clerk (dev/prod dual mode, auto-switches)
web = cycls.Web().auth(cycls.Clerk())

# Custom Clerk tenant
web = cycls.Web().auth(cycls.Clerk(
    jwks_url="https://clerk.mycompany.com/.well-known/jwks.json",
))

# Generic OIDC
web = cycls.Web().auth(cycls.JWT(
    jwks_url="https://my-prod.auth0.com/.well-known/jwks.json",
    dev_jwks_url="https://my-dev.auth0.com/.well-known/jwks.json",
))

Workspaces

Every user gets a personal workspace, and organizations can share team workspaces with role-based access. Each workspace is a full context: files, chats, AGENT.md, skills and key-value store.

web = cycls.Web().auth(cycls.Clerk()).workspaces()          # any member can create teams
web = cycls.Web().auth(cycls.Clerk()).workspaces(create="admin")

The active workspace is selected per request with the X-Workspace header. See docs/workspaces.md.

Instructions and Skills

Every turn, the harness reads AGENT.md from the user's workspace root and appends it to the system prompt as user preferences subordinate to your .system() prompt.

Skills are packs of task-specific instructions loaded on demand: only the name and description sit in the system prompt, and the body enters context when the model calls the skill tool.

image = cycls.Image().copy("skills/")
llm = cycls.LLM().skills("skills").instructions("AGENT.md")

User-created skills in skills/<name>/SKILL.md join the catalog automatically and win name collisions with shipped skills.

Analytics, Billing and Cost

web = (
    cycls.Web()
    .auth(cycls.Clerk())
    .analytics(cycls.PostHog(), cycls.GTM("GTM-ABCD123"))
    .notifications(cycls.OneSignal("<app-id>"))
    .cms(brand="https://cms.cycls.ai/agents/my-agent")
)

llm = cycls.LLM().price(input=3, output=15, cache_read=0.30, cache_write=6)

With prices set, every turn logs its cost:

cycls cost my-agent --by user
cycls sql 'SELECT ... FROM logs WHERE JSON_VALUE(json_payload, "$.level") = "usage"'

API Endpoints

Endpoint Format
POST / Cycls streaming protocol (SSE), /chat is an alias
POST /chat/completions OpenAI-compatible
GET /config app config (title, branding, auth flag)
GET /chats, PUT /chats/<id>, DELETE /chats/<id> chat history
GET /files, PUT /files/<path>, PATCH, DELETE per-user files
POST /share, GET /shared/<user>/<token> share links
GET /workspaces, POST /workspaces workspaces, when enabled
GET /connectors, POST /connectors/<name>/authorize connectors, when declared

State routers are installed only when auth is configured. Sessions and files live per user under /workspace/<scope>/.

HTTP Extension

Agents expose the underlying FastAPI surface via .server for webhooks, health checks, OAuth callbacks, and any custom routes:

from fastapi import Depends


@my_agent.server.api_route("/webhook", methods=["POST"])
async def stripe_webhook(request):
    payload = await request.json()
    return {"ok": True}


@my_agent.server.api_route("/profile", methods=["GET"])
async def profile(user=Depends(my_agent.auth)):
    return {"user_id": user.id}

Declarative Infrastructure

The cycls.Image primitive holds container build config. Every field is chainable, and the resulting Image is passed to any decorator via image=.

image = (
    cycls.Image()
    .pip("openai", "pandas", "numpy")
    .apt("ffmpeg", "imagemagick", "libpq-dev")
    .copy("./utils.py")
    .copy("./models/", "app/models/")
    .run("echo 'hello from build' > /app/build_marker.txt")
    .rebuild()                      # force a clean build
)

@cycls.function(image=image)
def my_func(x):
    from utils import helper_function   # bundled via .copy()
    ...
Method Purpose
.pip(*packages) install Python packages from PyPI
.apt(*packages) install system packages
.copy(src, dst=None) bundle local files and directories, dst defaults to src
.run(command) run a shell command during the build
.rebuild() skip the Docker cache

Images are content-hashed, including the contents of copied files, so identical inputs reuse the cached build and one changed package rebuilds only what depends on it.

Static files served at /public live on the Web primitive:

web = cycls.Web().copy_public("./assets/logo.png", "./downloads/")

Functions

For batch jobs, data processing, scheduled work and services without a chat UI:

@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())


simulate.run(1000)             # local Docker
simulate.remote(1000)          # cloud, current code
simulate.map([10**6] * 100)    # fan out, ordered results
simulate.deploy()              # frozen, callable by name
import cycls
pi = cycls.remote("simulate")(10_000_000)

A function that takes port deploys as a server on its own URL. A bare function deploys as a named endpoint callable from any machine with your API key.

Add schedule= and the platform fires the deployed function on a cron:

@cycls.function(schedule=cycls.Cron("0 3 * * *", timezone="Asia/Riyadh"),
                volumes={"/reports": cycls.Volume("daily-reports")})
def nightly():
    ...

Sandbox

The Bash tool runs inside a bubblewrap sandbox: read-only root, cleared environment, the workspace as the only writable path, the chat store masked, and the cloud metadata range blocked. Network is on by default so curl, pip and git work.

llm = cycls.LLM().sandbox(network=False).bash_timeout(120)

A prompt-injected shell can exfiltrate anything it can read, so turn the network off when the agent does not need it. Full threat model: docs/notes/sandbox-security.md.

What You Get

  • One file: primitives, code, and infrastructure together
  • Three decorators: @function, @app, @agent, each strict and composable
  • Multi-LLM: Anthropic native, OpenAI, open weight models, and your own servers
  • Managed loop: retries, compaction, sandbox, tool handlers, history, background runs
  • CLI + SDK: cycls run, cycls deploy, or programmatic .local() / .deploy()
  • No drift: what you see is what runs

No YAML. No Dockerfiles. No infrastructure repo. The code is the deployment.

Learn More

License

MIT

Metadata

Release files for cycls 0.0.2.146

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cycls 0.0.2.146
File Size Uploaded Provenance
cycls-0.0.2.146.tar.gz 5.3 MB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for cycls 0.0.2.146
File Interpreter ABI Platform
cycls-0.0.2.146-py3-none-any.whl Python 3 none any Details

Total release size: 10.9 MB

Release files / cycls-0.0.2.146.tar.gz

Download URL cycls-0.0.2.146.tar.gz
Size 5.3 MB
Tags Source
SHA-256 checksum
How to use checksums
4a801b61aa0cfca84d7a5dc72edbd1b4e5a3d442a60123f7a5278755eea99bb3
BLAKE2b-256 checksum
How to use checksums
a6c93f8031a77d873d121b7a5ccbd3590304531de57794e360f4f46b42a02ff3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / cycls-0.0.2.146-py3-none-any.whl

Download URL cycls-0.0.2.146-py3-none-any.whl
Size 5.6 MB
Tags Python 3
SHA-256 checksum
How to use checksums
6e4ae98f17492e721a8fd497012dbd38ac11a09d8aa56898bb3719ec3f601605
BLAKE2b-256 checksum
How to use checksums
6e77f38c936e5eaac5e4499ba015b18e09a5bebae2b636026660f979fb2c52f8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for cycls 0.0.2.146

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.0.2.146 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page