Capsule Documentation Audit
Capsule ships an llms.txt and a clean topical structure (start / build / features / concepts / reference), but for a "framework and hosted runtime" the operational connective tissue is thin: there is no machine-readable API surface, the changelog is effectively empty, the troubleshooting page promises six error categories and shows one, and several agent-relevant contracts (task callbacks, exception surfaces, channel binding rules) are sketched rather than specified.
1. No OpenAPI / machine-readable API spec for endpoints or Client (critical)
Location: https://docs.capsule.new/llms.txt; /reference/decorators.md (@app.endpoint(...)); /reference/client.md
Problem: The docs index at https://docs.capsule.new/llms.txt lists every documentation page but no openapi.json, openapi.yaml, or any machine-readable schema. The platform clearly has HTTP surfaces — the decorators reference documents @app.endpoint(method="GET", path="/", authorized=True) and @app.asgi(path=...), and /reference/client.md advertises "Programmatic access to deployed Capsule apps with cpsl.Client" — yet there is nothing for an AI agent or codegen tool to consume programmatically. Endpoint shapes, status codes, auth behavior, and Client method signatures are prose-only.
Consequence: Coding agents (Claude Code, Cursor, Copilot) consuming Capsule docs cannot enumerate endpoints, generate clients, or validate request/response shapes. Anyone building a Capsule integration in another language has to reverse-engineer the wire format from Python prose. Type-aware tooling (mypy plugins, IDE completions) can't be auto-generated.
The fix: Publish an OpenAPI 3.x document for both (a) the workspace/control-plane API the CLI talks to and (b) the in-app HTTP surface produced by @app.endpoint(...). Link both from llms.txt and from the Client reference page. Include authorized=True semantics (auth header name, token source) as a Security scheme.
2. Changelog is essentially empty for a hosted runtime (significant)
Location: /changelog.md
Problem: The page describes itself as "Notable Capsule documentation and platform updates" and contains exactly one entry, dated 2026-04-24, whose bullets are all about restructuring the docs ("Reworked the docs around a clearer path", "Added feature-oriented pages", "Added pricing and payments documentation"). There are no SDK version entries, no runtime version entries, no breaking-change notes. As of 2026-05-15 — roughly three weeks later — nothing else has been added, and the page ends with a "Use this format for future entries" template that hasn't been populated.
Consequence: For a product where local cpsl and a hosted runtime drift independently (the existence of /sdk-runtime-versions.md confirms this is a real concern), developers and agents have no authoritative record of what changed when. "Did the integration callback shape change last week?" is unanswerable from the docs. The only changelog signal currently visible is meta-information about doc reorganization, which is irrelevant to anyone debugging behavior.
The fix: Backfill SDK and runtime release notes from the platform's actual ship history — every cpsl version bump, every runtime change that affects user code (decorator behavior, collection semantics, integration handshake), and every CLI command change. Auto-generate from release tags if possible. Separate documentation-only entries from platform entries with clear headings so the latter is scannable.
3. Troubleshooting promises six error categories, ships one (significant)
Location: /troubleshooting.md
Problem: The page lede says: "Common setup, serve, deploy, auth, secret, and runtime issues." That's six explicit categories. The only visible section is ## capsule command not found — a setup/PATH issue. There is no troubleshooting entry covering serve failures, deploy failures, capsule login auth problems, secret resolution errors, or runtime errors from inside a deployed app.
Consequence: A developer hitting a real failure mode (failed deploy, integration timeout, secret not found in runtime, runtime version mismatch between local serve and hosted deploy) has no troubleshooting entry to consult. They escalate to support or guess. Agents trying to recover from a CLI failure can't pattern-match the error against any documented fix.
The fix: Add at least one worked entry per category named in the lede. Pull from real support tickets if available. For each: the exact error message string, the cause, and the resolution. Cross-link from the matching reference page (capsule deploy failures should link from /reference/cli; integration failures should link from /features/integrations).
4. Exception types listed without a unified error reference (significant)
Location: /reference/session-and-request-context.md (Exceptions section)
Problem: Three exception classes are listed flat — IntegrationTimeout, IntegrationDeclined, FileUploadTimeout — with no fields, no inheritance hierarchy, no module path, and no guidance on when each is raised vs. when a coroutine simply returns. Collections operations like find_one, update_one, delete_one documented on /reference/collections-and-settings.md have no documented exception contract — does find_one raise on no match or return None? Does update_one raise on a filter that matches zero documents?
Consequence: Developers writing try/except blocks have no way to know what to catch. Agents generating Capsule code will either over-catch Exception or omit error handling entirely, both of which produce fragile apps. Workflows and tasks that need to retry on specific failures cannot be written defensively.
The fix: Add a single /reference/exceptions.md page listing every exception the SDK can raise, the module it lives in (e.g. cpsl.exceptions.IntegrationTimeout), what triggers it, and which APIs raise it. Document the no-match / no-affected-row contract for every CollectionRef method.
5. @app.task(...) callback_url has no payload contract (significant)
Location: /reference/decorators.md (task arguments table)
Problem: The task argument table documents callback_url as "URL to POST completion/failure payloads to" and stops there. The payload shape, the HTTP headers, the content-type, the retry policy on the receiving side, the auth/signing mechanism (is there an HMAC? a shared secret? nothing?), and the distinction between completion and failure payloads are all absent. The neighboring arguments (retries, timeout, lock, retry_for, process) are similarly one-line, but only callback_url describes an external integration contract that the developer's own server has to implement.
Consequence: Any developer wiring callback_url to a service they control has to deploy, fire a real task, and inspect the request to discover the payload — there is no way to write a typed handler from the docs alone. Agents asked to "implement the callback receiver" will hallucinate a shape. Without a documented signing mechanism, callback receivers either accept unauthenticated POSTs from the internet or build their own out-of-band auth.
The fix: Add a callback_url subsection under @app.task(...) showing: a complete example completion payload, a complete example failure payload, the request headers (including any signature header and the algorithm used to compute it), the retry policy Capsule applies if the receiver returns non-2xx, and the timeout. Publish the payload schema in the OpenAPI document recommended in Issue 1.
6. Decorator availability gated on Image(...) is asserted but unexplained (minor)
Location: /reference/decorators.md
Problem: The decorator table is introduced with the parenthetical: "Functional app decorators (require App(..., image=cpsl.Image()))". This implies every functional decorator (@app.boot(), @app.message(), @app.task(...), @app.schedule(cron), @app.endpoint(...), @app.asgi(...)) is unavailable without passing an Image. The failure mode when image= is omitted is not stated anywhere in the evidence — the docs neither show the exception nor describe whether registration silently no-ops.
Consequence: A developer following /build/first-chat-app may omit image=cpsl.Image() and hit an undocumented failure. Agents reading this line cannot tell whether to always emit image=cpsl.Image() defensively.
The fix: State explicitly which decorators require image=, and document the exact failure mode when it's missing. Update /build/first-chat-app and /concepts/app-model-and-runtime to show the minimal App(..., image=cpsl.Image()) pattern with a one-line explanation.
7. Channel-to-token binding rule is buried and Telegram-only (minor)
Location: /reference/channels-integrations-and-secrets.md vs. /features/channels.md and /reference/cli.md
Problem: The reference page surfaces an important operational rule scoped to Telegram only: "One named Telegram channel maps to one Telegram bot token and one bound app deployment. Avoid token reuse across multiple channel resources unless intentionally migrating webhooks." The CLI reference documents three channel types — telegram, slack, whatsapp — but the binding rule is asserted for Telegram and silent on Slack and WhatsApp. Either the same one-resource-one-credential constraint applies to all three (in which case the rule should be generalized) or the constraint is Telegram-specific (in which case Slack and WhatsApp need their own analogous rules documented). Additionally, the rule lives in reference, not in /features/channels.md (the page a developer reads when first setting up a channel) and not in the capsule channel create / capsule channel bind CLI entries.
Consequence: A developer creating two channels with the same Telegram bot token (e.g., staging and prod sharing one bot) will silently fight the webhook binding. A developer doing the same with a Slack or WhatsApp credential has no documented guidance at all — the asymmetry leaves them guessing. By the time anyone consults the reference page, they've already misconfigured.
The fix: Either generalize the binding rule across all three channel types and surface it as a callout on /features/channels.md and the capsule channel create and capsule channel bind CLI entries, or explicitly document the per-channel-type constraint for Slack and WhatsApp alongside the Telegram one. Reference can keep the longer version.
8. current_session() documented as a one-liner with no usage rules (minor)
Location: /reference/session-and-request-context.md (current_session())
Problem: The entire documentation for current_session() reads: "Returns the active runtime Session when available." When is it not available? Inside @app.boot() and @app.shutdown() (which run outside any session)? Inside @app.task(...) invoked by a schedule rather than a user message? Inside @app.schedule(cron)? Inside @app.endpoint(...) for unauthenticated requests? Does it return None, raise, or both depending on context?
Consequence: This is the classic context-variable footgun. Developers will write helpers that call current_session() and work fine in @app.message() but None-dereference in @app.task() or @app.boot(). Agents generating helper utilities have no signal that the function is conditional. The bug surfaces in production, not at boot.
The fix: Document the return contract explicitly: which decorators are guaranteed to have an active session, which guarantee no session, which are conditional. State whether the function returns None or raises when no session exists, and link to or define the exception used.
What they do well
- llms.txt exists and lists every docs page with
.mdURLs — agents can index the site cleanly. - The split between "builder auth" (
capsule login) and "app user auth" (session.user) is called out explicitly on/concepts/auth-and-scopes, which is exactly the kind of distinction most platforms blur. - The four-scope model for collections (
app/user/owner/session) is documented as a table on/reference/collections-and-settings, which is parseable both for humans and for agents.
Top 3 recommendations
- Publish an OpenAPI spec for the control plane and for
@app.endpoint(...)surfaces, and include the@app.task(...)callback_urlpayload schema; link it from llms.txt. This is the single highest-leverage change for agent consumers. - Backfill the changelog with real SDK and runtime entries — even retroactively — and commit to weekly entries going forward. The current state implies the platform isn't shipping.
- Build a real troubleshooting catalog and a unified exceptions reference, and pin down the context-availability contract for
current_session(). Together they convert Capsule from "happy path documented" to "production-debuggable."