Skip to main content
cycls.Web configures everything the user sees and the identity behind it. Pass it to @cycls.agent(web=...).
A Cycls agent empty state with brand name, description and composer

The empty-chat screen uses the brand name, description and logo.

Branding

name, description, logo and brand are per locale, so call .brand() again with "ar" for Arabic. Image paths are read at build time, SVGs inlined and rasters embedded as data URIs, so assets do not need to ship with the container.

Pulling copy from a CMS

These are plain GET URLs returning the contract JSON. Anything declared in code wins field by field, so a .brand() call overrides the CMS for exactly that field and no other.

Theme and colors

primary drives highlights and active states, secondary drives chips and bubbles. The _dark variants override dark mode and default to the light values. Fonts are two CSS variables: --font-display for the agent’s name, the names in the agents dropdown and page titles, and --font-sans for everything else. Both default to the system font stack, and --font-display follows --font-sans until you set it. Set them from .head(), and serve the font files with .copy_public():

The empty-chat screen

Prompt starters, off by default.

SEO and social

.seo() covers the page title and meta description when they should differ from the brand copy. .head() appends raw HTML to <head> and is repeatable. Every agent also serves /og.png, a generated social preview, plus /robots.txt, /sitemap.xml and /llms.txt.

Mobile app

Points phone visitors to the Cycls app. Each link must be an https:// store URL. On iOS or Android, a bar under the header reads “Cycls App, For a better experience” with an Open button to that platform’s link. The bar names the Cycls app and shows its icon, whatever your branding. Dismissing it or tapping Open hides it for 14 days. Each browser’s own prompt comes first:
  • An App Store link adds the apple-itunes-app meta tag, so Safari on iPhone and iPad shows Apple’s banner instead of the bar.
  • A Play link serves /manifest.webmanifest, so on Android, Open can bring up Chrome’s install sheet.

Static files

Files keep their relative paths, so the logo is served at https://your-agent.cycls.ai/public/assets/logo.png.

Uploads

Enforced server-side and pre-checked in the client so oversized files fail fast.

Voice input

The mic in the composer records, and your server turns the recording into text with a speech-to-text model. The browser sends audio and gets the words back. It never learns which service or model heard them, and never holds a key. .voice() names the model as vendor/model, the way cycls.LLM does, with the service’s base_url. Any service that speaks OpenAI’s audio transcriptions API works:
Without .voice(), the mic uses OpenAI’s gpt-4o-transcribe whenever OPENAI_API_KEY is set, and is hidden when it is not. Every vendor but OpenAI needs a base_url. The key is api_key, or <VENDOR>_API_KEY from the server’s environment. Use headers= for an endpoint that authenticates another way, such as a proxy. Through OpenRouter, the same call reaches ElevenLabs, Deepgram, AssemblyAI and Google models, and open ones such as Whisper, Voxtral and Nemotron. List them with GET https://openrouter.ai/api/v1/models?output_modalities=transcription.

Analytics and push

.analytics(True) is shorthand for the PostHog default. Providers are plugins over one canonical event pipe, and each can be scoped with an events allowlist of the names below. .notifications() adds web push, where the permission card is ours and the trigger is a platform decision. PostHog identifies signed-in people with their name, email, organization and plan. The plan is the token’s pla claim, the same value as context.user.plan. A u: plan sets user_plan and user_is_paid, and an o: plan sets org_plan and org_is_paid, so switching between personal and an organization updates only that pair. Paid means any plan except u:free_user and o:free_org.
Since 0.0.2.145, the plan_name, plan_status, plan_amount, plan_period, plan_period_end, plan_canceled_at and is_paid person properties are no longer set, and the subscription_cancelled and subscription_resumed events are gone. Move dashboards to the plan properties above.

Reacting to finished runs

Called once when a run reaches any terminal status: done, stopped, interrupted or failed. Use it for push, email, a webhook or a row in a table. Exceptions are logged and never reach the run. error_id and taken_back are present only when they apply.

Method reference

Next

Authentication

Clerk, any OIDC provider, and what lands in context.user.