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.127.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.127-py3-none-any.whl (3.3 MB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: cycls-0.0.2.127.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.127.tar.gz
Algorithm Hash digest
SHA256 be2fc2cd6b291ab3899d4d078fe858c3c0d8fb1b209466d0427aec5b4886d658
MD5 0e5a51a8db363fe3a32a9efc0407335c
BLAKE2b-256 e5e975196f36ed06aa4d3dd294537607a1b6f681aeeb147ffe971c3452c0fc00

See more details on using hashes here.

File details

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

File metadata

  • Download URL: cycls-0.0.2.127-py3-none-any.whl
  • Upload date:
  • Size: 3.3 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.127-py3-none-any.whl
Algorithm Hash digest
SHA256 f85a7cfe770181cfd13fe515f769f3a1852aac332a678ef7cac7127d114bf3b7
MD5 c263d4381f22ffb716c1af62acfff36b
BLAKE2b-256 b32a63cf40792905f8f8e86299d469306e4a66a09d9ad12520cfaed5ce349b0c

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