Distribute Intelligence
Website | Docs | Functions | Volumes | Cron | CLI
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. Three primitives compose it. Deploy it with one command.
import cycls
image = cycls.Image().copy(".env")
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"])
)
@cycls.agent(image=image, web=web)
async def my_agent(context):
async for msg in llm.run(context=context):
yield msg
cycls deploy my_agent.py # live at https://my-agent.cycls.ai
Installation
pip install cycls
See docs/function.md for the full function interface, and docs/cli.md for the CLI.
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, billing, analytics
cycls.LLM — model, system prompt, tools, runtime config
cycls.Clerk — Clerk JWT auth provider (or cycls.JWT for generic OIDC)
Decorators (compose primitives into deployable units):
@cycls.function(image=) — non-blocking compute
@cycls.app(image=) — blocking ASGI service
@cycls.agent(image=, web=) — managed chat product
CLI:
cycls run file.py — local Docker with hot-reload
cycls deploy file.py — production deploy
cycls ls — list deployments
cycls logs <name> -f — tail logs
cycls rm <name> — delete a deployment
cycls init [name] — scaffold a starter agent
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 deploy my_agent.py # production
Get an API key at cycls.com.
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 (Auth0, WorkOS, etc)
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",
))
@cycls.agent(web=web)
async def my_agent(context):
user = context.user # User(id, org_id, plan, features, ...)
...
Analytics & Billing
web = (
cycls.Web()
.auth(cycls.Clerk())
.analytics(True) # usage metrics on the Cycls dashboard
.cms("cycls.ai") # CMS entry → monetize via Cycls Pass subscriptions
.title("My Agent")
)
Custom Tools
Tools are bare JSON schemas. Handlers are plain async functions registered via .on(name, handler). Handler return values flow to both the UI stream and the LLM'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": "text", "text": f""}
llm = (
cycls.LLM()
.model("anthropic/claude-sonnet-4-6")
.tools(TOOLS)
.on("render_image", render_image)
)
Multi-provider LLM
One adapter covers Anthropic natively and every OpenAI-compatible endpoint (OpenAI, Groq, vLLM, HUMAIN, self-hosted, ...) via provider/model strings:
cycls.LLM().model("anthropic/claude-sonnet-4-6") # Anthropic native
cycls.LLM().model("openai/gpt-5.4") # OpenAI
cycls.LLM().model("groq/llama-3.3-70b").base_url(...) # Groq or any OpenAI-compat
cycls.LLM().model("humain/jais").base_url(...) # sovereign inference
Thinking/reasoning events, tool calls, and streaming are unified across providers.
Streaming Components
Yield structured objects from an agent body for rich streaming responses:
@cycls.agent(web=cycls.Web().auth(cycls.Clerk()))
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 | Streaming |
|---|---|
{"type": "thinking", "thinking": "..."} |
Yes |
{"type": "code", "code": "...", "language": "..."} |
Yes |
{"type": "table", "headers": [...]} / {"type": "table", "row": [...]} |
Yes |
{"type": "status", "status": "..."} |
Yes |
{"type": "callout", "callout": "...", "style": "..."} |
Yes |
{"type": "image", "src": "..."} |
Yes |
Thinking Bubbles
The {"type": "thinking", ...} component renders as a collapsible thinking bubble. Consecutive thinking yields append to the same bubble until a different component type is yielded. Cycls automatically maps provider reasoning deltas (Claude extended thinking, OpenAI delta.reasoning) to this channel, so you get thinking bubbles without doing anything special.
Context Object
@cycls.agent(web=cycls.Web().auth(cycls.Clerk()))
async def chat(context):
context.messages # [{"role": "user", "content": "..."}]
context.messages.raw # Full data including UI component parts
context.user # User(id, org_id, plan, features, ...) when auth is set
context.prod # True via .deploy(), False via .local() — gate billing/analytics
with context.workspace(): # Per-user persistent scope — enables cycls.Dict(...)
usage = cycls.Dict("usage")
API Endpoints
| Endpoint | Format |
|---|---|
POST /chat/cycls |
Cycls streaming protocol |
POST /chat/completions |
OpenAI-compatible |
HTTP Extension
Agents expose the underlying FastAPI surface via .server for webhooks, health checks, OAuth callbacks, and any custom routes:
@cycls.agent(web=cycls.Web().auth(cycls.Clerk()))
async def my_agent(context):
async for msg in llm.run(context=context):
yield msg
@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; the resulting Image is passed to any decorator via image=.
image = (
cycls.Image()
.pip("openai", "pandas", "numpy", "transformers")
.apt("ffmpeg", "imagemagick", "libpq-dev")
.copy("./utils.py")
.copy("./models/", "app/models/")
.copy("/absolute/path/to/config.json")
.run("echo 'hello from build' > /app/build_marker.txt")
)
@cycls.function(image=image)
def my_func(x):
from utils import helper_function # bundled via .copy()
...
.pip(*packages) — Python packages
Install any packages from PyPI during container build.
cycls.Image().pip("openai", "pandas", "numpy", "transformers")
.apt(*packages) — System packages
Install apt-get dependencies. Need ffmpeg? ImageMagick? Declare it.
cycls.Image().apt("ffmpeg", "imagemagick", "libpq-dev")
.copy(src, dst=None) — Bundle files
Include local files and directories. Works with relative or absolute paths, single files or whole trees. dst defaults to src; pass both to relocate.
(
cycls.Image()
.copy("./utils.py") # same path
.copy("./models/", "app/models/") # src → dst
.copy("/home/user/configs/app.json") # absolute
)
Import bundled modules in your function body:
@cycls.function(image=cycls.Image().copy("./utils.py"))
def my_func(x):
from utils import helper_function
...
.run(command) — Build-time shell commands
cycls.Image().run("pip install --upgrade pip").run("apt-get clean")
.rebuild() — Force Docker cache bust
image = cycls.Image().pip("numpy").rebuild() # skip Docker cache
Public static files via cycls.Web
Static files served from /public (images, downloads, assets) live on the Web primitive:
web = cycls.Web().copy_public("./assets/logo.png", "./downloads/")
Access them at https://your-app.cycls.ai/public/logo.png.
What You Get
- One file — Primitives, code, and infrastructure together
- Three decorators —
@function,@app,@agent, each one strict and composable - Multi-LLM — Anthropic native + every OpenAI-compatible endpoint
- Managed loop — retries, compaction, sandbox, tool handlers, history, sessions
- 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
- Functions — the function interface, end to end
- Volumes — persistent, shareable storage for deployments
- Cron — fire a deployed function on a schedule
- CLI — every command
- Tutorial — comprehensive guide from basics to advanced
- Sandbox security — how the Bash tool is isolated
- Examples — working code samples
License
MIT
Release files for cycls 0.0.2.140
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cycls-0.0.2.140.tar.gz | 3.0 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cycls-0.0.2.140-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 6.3 MB
Release files / cycls-0.0.2.140.tar.gz
| Download URL | cycls-0.0.2.140.tar.gz |
|---|---|
| Size | 3.0 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8cdb1dbcf969db847cd3433cfc7e413ad74049af17904a5b8f3c452034f247df
|
|
BLAKE2b-256 checksum How to use checksums |
a0eb874118fe6baa28a68c6566475a8404892bbe77384d9f298f285b1918e126
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.25 {"installer":{"name":"uv","version":"0.9.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","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.140-py3-none-any.whl
| Download URL | cycls-0.0.2.140-py3-none-any.whl |
|---|---|
| Size | 3.3 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9ba8521e10f8d5eded228d36e27ac501b7546f38747f7a40e859287cef71f8c0
|
|
BLAKE2b-256 checksum How to use checksums |
2e12470e79b23958d4da97b1de7f18eb4c9ff3f05fa4066944128be196a96c60
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.25 {"installer":{"name":"uv","version":"0.9.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|