Skip to main content

Distribute Intelligence

Project description

Distribute Intelligence

Website | Docs | Tutorial

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. 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"![image]({args['src']})"}


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 + SDKcycls 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

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

cycls-0.0.2.125.tar.gz (3.0 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cycls-0.0.2.125-py3-none-any.whl (3.2 MB view details)

Uploaded Python 3

File details

Details for the file cycls-0.0.2.125.tar.gz.

File metadata

  • Download URL: cycls-0.0.2.125.tar.gz
  • Upload date:
  • Size: 3.0 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

Hashes for cycls-0.0.2.125.tar.gz
Algorithm Hash digest
SHA256 50c2e7da9473ddad2fec043d5befe09d71c83677fdf77d0b19170ca4f544509a
MD5 acbf9b31abf3df0e30e102e9118773b4
BLAKE2b-256 484d174574f6381c9840e2749dbf503a1252a0edcfa2ca7b0533bb7081661956

See more details on using hashes here.

File details

Details for the file cycls-0.0.2.125-py3-none-any.whl.

File metadata

  • Download URL: cycls-0.0.2.125-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

Hashes for cycls-0.0.2.125-py3-none-any.whl
Algorithm Hash digest
SHA256 d535d8123dd3b052cc19eb930967f24a0d33b28ff0288bb90514316c44546397
MD5 7118a4171b109d46c8ef483d544cb7f1
BLAKE2b-256 18ff04a2689e2b37548978ae82ef83a48e5db9dbe23851f7877dcfff3a14ccbe

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page