Distribute Intelligence
Project description
Distribute Intelligence
Website | Docs | Tutorial
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
Requires Docker. See the full tutorial for a comprehensive guide.
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
- Tutorial — comprehensive guide from basics to advanced
- Sandbox threat model — how the Bash tool is isolated
- Examples — working code samples
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cycls-0.0.2.124.tar.gz.
File metadata
- Download URL: cycls-0.0.2.124.tar.gz
- Upload date:
- Size: 2.9 MB
- Tags: Source
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a0aa299cb8ad67b8db933b1c5c68251cd4426efa6b3eb8cc6ec3f06a13a01923
|
|
| MD5 |
8ba4575e0201c147747d7f537d00d763
|
|
| BLAKE2b-256 |
6167d3027bba92b4b69330869886e9846675a02b8a117aaa043f7c800d0a77ac
|
File details
Details for the file cycls-0.0.2.124-py3-none-any.whl.
File metadata
- Download URL: cycls-0.0.2.124-py3-none-any.whl
- Upload date:
- Size: 3.2 MB
- Tags: Python 3
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9a689fe634ea19d41a3c5e2ff1b8a67f596305b319a19d377f7c0780ba0aa1c6
|
|
| MD5 |
87d1cf612f5fbcddc2d1aeb7467b0983
|
|
| BLAKE2b-256 |
1f9e6c9403f63de33cb2e40ccab1818f821d6dd4d9f1759183d93d1fdd0dd5d0
|