reboot/plugin/skills/python/SKILL.md
Reboot Python framework for building transactional microservices with durable actor state. APIs are defined in pydantic Python (`reboot.api`). Use this skill when writing Python code for a Reboot application, defining APIs with reader/writer/transaction/workflow methods, implementing Servicers, calling actor refs across services, scheduling work, building durable workflows with the right call primitive (`.per_workflow(alias)` / `.per_iteration(alias)` / `.always()` for Reboot calls; `at_least_once` / `at_most_once` for external calls; `until` / `until_changes` for reactive waiting on Reboot state), calling an LLM / building an AI agent in the backend via the durable `reboot.agents.pydantic_ai.Agent`, or testing Reboot applications with the `Reboot()` test harness.
npx skillsauth add reboot-dev/reboot pythonInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Guide for building transactional microservices in Python with the Reboot
framework. Reboot APIs are defined in pydantic Python
(reboot.api) and code-generated into typed Servicer base classes;
you implement async methods that receive a typed context
(ReaderContext, WriterContext, TransactionContext, or
WorkflowContext).
Reference these guidelines when:
.rbtrc, pyproject.toml,
application entry point)Service.ref(id).method(context, ...)WorkflowContext, picking the right
primitive per servicer-workflow.md (Reboot calls use
.per_workflow(alias) / .per_iteration(alias) / .always();
external calls default to at_least_once, with at_most_once for
the rare non-retryable call)
plus until / until_changes for reactive waitingref.schedule(when=...).method(context)reboot.agents.pydantic_ai.AgentOrderedMap, mailgun, etc.)Reboot() harness| Priority | Category | Impact | Prefix |
| -------- | ---------- | ---------- | ------------- |
| 1 | Lifecycle | CRITICAL | lifecycle- |
| 2 | API | CRITICAL | api- |
| 3 | Servicer | HIGH | servicer- |
| 4 | Agent | HIGH | agent- |
| 5 | Stdlib | HIGH | stdlib- |
| 6 | Crypto | HIGH | crypto- |
| 7 | State | HIGH | state- |
| 8 | Auth | HIGH | auth- |
| 9 | RPC | MEDIUM | rpc- |
| 10 | Scheduling | MEDIUM | scheduling- |
| 11 | Testing | LOW-MEDIUM | testing- |
| 12 | Patterns | LOW-MEDIUM | patterns- |
The Workflow(...) context method is the fourth servicer context
type alongside reader / writer / transaction; its (large) reference
is servicer-workflow.md.
A Reboot Servicer subclasses the generated <Type>.Servicer base class and
implements one async def per RPC. Each method takes a typed context as the
second argument; that type is determined by the API method factory
(Reader/Writer/Transaction/Workflow):
from chat_room.v1.chat_room_rbt import ChatRoom
from reboot.aio.contexts import ReaderContext, WriterContext
class ChatRoomServicer(ChatRoom.Servicer):
# No `authorizer()` defined — fine for `rbt dev` (the runtime
# warns and allows). Before going to production, add a
# `TokenVerifier` and a real `authorizer()` rule (see
# references/servicer-authorizer.md).
async def messages(
self,
context: ReaderContext,
) -> ChatRoom.MessagesResponse:
return ChatRoom.MessagesResponse(messages=self.state.messages)
async def send(
self,
context: WriterContext,
request: ChatRoom.SendRequest,
) -> None:
self.state.messages.append(request.message)
A Reboot application's main constructs an Application with the list of
Servicer classes and runs it under asyncio:
import asyncio
from reboot.aio.applications import Application
from reboot.aio.external import InitializeContext
from chat_room.v1.chat_room_rbt import ChatRoom
from chat_room_servicer import ChatRoomServicer
async def initialize(context: InitializeContext):
# Implicitly construct the singleton on first write.
await ChatRoom.ref("reboot-chat-room").send(context, message="Hello!")
async def main():
await Application(
servicers=[ChatRoomServicer],
initialize=initialize,
).run()
if __name__ == '__main__':
asyncio.run(main())
Never hand-edit generated *_rbt.py files. The pydantic API definition
file is the source of truth (see references/api-pydantic.md):
from reboot.api import API, Field, Methods, Model, Reader, Type, Writer
# Every Field needs an explicit default (see Key Constraints below).
class ChatRoomState(Model):
messages: list[str] = Field(tag=1, default_factory=list)
class SendRequest(Model):
message: str = Field(tag=1, default="")
class MessagesResponse(Model):
messages: list[str] = Field(tag=1, default_factory=list)
api = API(
ChatRoom=Type(
state=ChatRoomState,
methods=Methods(
messages=Reader(request=None, response=MessagesResponse, mcp=None),
send=Writer(request=SendRequest, response=None, mcp=None),
),
),
)
rbt generate (run automatically by rbt dev run) emits
<pkg>/<v>/<name>_rbt.py with the <Type> class, request/response
messages nested as attributes, the Servicer base class, and the
.ref(id) factory.
Reader(...) requires ReaderContext; Writer(...)
requires WriterContext; Transaction(...) requires
TransactionContext; Workflow(...) requires WorkflowContext.self.state is read-only inside ReaderContext. Mutate it only inside
WriterContext or TransactionContext. Workflows mutate state by
calling Service.ref().write(context, callback) — not self.state —
because workflows can re-execute on replay..rbtrc is line-based, not YAML. Each line is <subcommand> <flag>.
Use --application-name=<app> (canonical since Reboot 1.0.4; --name
still works as a deprecated alias but warns).self.ref().state_id inside writer/reader/
transaction methods, and context.state_id inside workflows.
self.state_id does not exist and raises AttributeError.Field(tag=N) needs an explicit zero-value default
(default="", default=0, default=0.0, default=False,
default_factory=list, etc.). Two layered rules:
(1) model_construct() drops fields lacking declared defaults, so
reads AttributeError; (2) only the type's zero value is accepted —
non-zero defaults raise UserPydanticError at import time. Set
domain defaults (turn="r", delay=1.0, etc.) inside the
constructor method, not on the Field. Applies to state,
request/response, and error Models.TransactionContext
(one-shot) or WorkflowContext (durable, long-running).await ref.deposit(context, amount=10), not
await ref.deposit(context, DepositRequest(amount=10)).Queue, Topic, OrderedMap, Presence, Item are
stdlib actor names — use them, don't redefine them. If a
design or task names any of these (e.g. "publish to a
Topic", "track members in an OrderedMap", "subscribe a
Queue", "presence shows who's online"), the answer is to
import the stdlib actor — not to declare a pydantic Model
with the same name. Defining your own Topic / Queue /
etc. forfeits durability, ordering, blocking semantics, and
concurrency guarantees the stdlib already provides. See the
trigger table under "How to Use → Using stdlib state types"
below.Most footguns in this skill are distributed across reference files — skipping the right reference means hitting a runtime error that the docs would have prevented. Before writing code, load the references the task actually requires from the lists below. "Common gotchas" should be loaded for almost every task.
references/servicer-workflow.md — the single, comprehensive
workflow reference; read it top to bottom before writing the body.
Covers the @classmethod / WorkflowContext declaration shape and
scheduling; the call-classification model that routes each call
to the right primitive (Reboot scope .per_workflow(alias) /
.per_iteration(alias) / .always() vs. external at_least_once /
at_most_once); context.loop(...) iteration; inline state
mutation via Service.ref().<scope>.write(context, fn);
until / until_changes reactive waiting; and the declared-vs-
undeclared exception rule for how a workflow exitsBackend LLM calls — chat completions, AI agents, tool-using
assistants — go through the durable reboot.agents.pydantic_ai.Agent,
never a raw anthropic / openai SDK or a bare
pydantic_ai.Agent. A raw call re-hits (and re-bills) the provider
on every workflow replay. Read both before writing agent code:
references/agent-pydantic-ai.md — always read for any
backend LLM work: constructing the Agent, the required
name=, running it inside a WorkflowContext, variant= for
repeated runs, and the streaming / replay caveatsreferences/agent-tools.md — @agent.tool /
@agent.tool_plain so the agent can read and mutate Reboot
stateThe Agent runs only inside a WorkflowContext, so also read the
"Building a workflow" references above.
references/api-pydantic.md — always read: the zero-default
rule bites at import timereferences/api-methods.md — Reader / Writer / Transaction /
Workflow factories and the factory=True constructor optionreferences/api-errors.md — typed error declarationreferences/state-collections.md — always read when modeling
any collection: decide whether each contained item should be
its own state Type, and pick between in-state list[Sub],
in-state list[str] of foreign IDs, or an OrderedMap of
foreign IDs. The most common modeling mistake — packing an
entity collection like list[Person] into one parent's state —
fails silently in demos and forces a full rewrite later.references/state-nested-models.md — same rule from the nested-
Model angle: state Models never appear as fields of other
state Modelsreferences/servicer-{reader,writer,transaction,workflow,constructor,authorizer}.md
— one per context type and the constructor / authorizer concerns.
servicer-workflow.md is the large one — see "Building a workflow"
above for when to reach for itreferences/rpc-refs.md — always read: self.ref().state_id vs.
self.state_id (which doesn't exist) is a recurring tripreferences/rpc-calls.md — always read: the kwargs-not-Request
trap (await ref.deposit(context, DepositRequest(amount=10))) is a
recurring miss; passes type-check and fails at runtimereferences/rpc-constructor-calls.md — always read when the
agent invokes a constructor: use <X>.create(context, id, ...) or
<X>.<CtorMethod>(context, id, ...), NEVER <X>.ref(id).<ctor>(...)
(the trap that skips creation semantics). The factory-declaration
side is servicer-constructor.md; the call-site side is hereIf your design calls for any of the concepts in the left column,
the stdlib already provides the canonical actor. Read the
reference before writing your own actor type — defining your
own Queue / OrderedMap / etc. is almost always wrong
and forfeits durability, ordering, and concurrency guarantees:
| You need... | Use | Reference |
| ------------------------------------------------- | ------------ | ----------------------- |
| Durable FIFO — work queue, job queue, intake | Queue | stdlib-queue.md |
| Sorted key-value with pagination / ordering | OrderedMap | stdlib-ordered-map.md |
| Presence — who's online / connected | Presence | stdlib-presence.md |
| Pubsub / broadcast / fan-out to subscribers | PubSub | stdlib-pubsub.md |
| Item builder for Queue / PubSub payloads | Item | stdlib-item.md |
| Encrypt at rest / crypto-shred (right-to-erasure) | Ciphertext | stdlib-ciphertext.md |
Each stdlib reference also lists its library registration —
forgetting <thing>_library() and the stdlib actor's
<thing>.servicers() in your Application(...) fails at boot
with "unknown actor type."
references/stdlib-ciphertext.md — encrypting sensitive data at
rest or crypto-shredding (GDPR right-to-erasure): the
Ciphertext library handles envelope encryption, per-scope erasure,
and automatic root-key rotation for you. Reach here before
hand-rolling any encryption.references/crypto-root-keys.md — only when building your own
key-deriving library/feature: HKDF-deriving purpose-specific keys
from Reboot's managed root keys, and the rotation control loop +
use_root_key_version / disuse_root_key_version markers. Most apps
want Ciphertext instead.references/auth-allow-deny.md — minimal/correct shapereferences/auth-allow-if.md and references/auth-built-in-predicates.md
— composition patternsreferences/auth-custom-predicates.md — for app-specific rulesreferences/testing-project-setup.md — backend/tests/ layout,
.pytest.ini, dev-deps, uv run pytestreferences/testing-harness.md — Reboot() harness, multi-servicer
Application(...), permissive authorizers, bearer tokens,
_auto_constructreferences/testing-external-context.md — create_external_context,
asserting on <Method>Aborted, waiting on tasks/workflows, mocking
external services / LLMs, one-test-per-user-storyreferences/patterns-common-gotchas.md — the consolidated trip-list
including the pydantic zero-default rule, self.state_id
non-existence, and --name vs. --application-name. Read it once
per task.Reference files live in references/ and are named
{prefix}-{topic}.md (e.g., servicer-transaction.md). Load only
the files relevant to the current task; the "How to Use" section
above lists the right ones grouped by task type. The full catalog:
Lifecycle (lifecycle-):
references/lifecycle-project-setup.mdreferences/lifecycle-rbtrc.mdreferences/lifecycle-application-entry.mdreferences/lifecycle-initialize-hook.mdreferences/lifecycle-secrets.mdreferences/lifecycle-dockerfile.mdreferences/lifecycle-reboot-cloud.mdAPI (api-):
references/api-pydantic.mdreferences/api-methods.mdreferences/api-errors.mdServicer (servicer-):
references/servicer-reader.mdreferences/servicer-writer.mdreferences/servicer-transaction.mdreferences/servicer-workflow.md — the single, comprehensive
workflow reference (declaration, call-classification, scopes,
context.loop, external at_least_once / at_most_once,
until / until_changes, and workflow exit semantics)references/servicer-constructor.mdreferences/servicer-authorizer.mdAgent (agent-):
references/agent-pydantic-ai.mdreferences/agent-tools.mdStdlib (stdlib-):
references/stdlib-ordered-map.mdreferences/stdlib-queue.mdreferences/stdlib-pubsub.mdreferences/stdlib-presence.mdreferences/stdlib-item.mdreferences/stdlib-ciphertext.mdCrypto (crypto-):
references/crypto-root-keys.mdState (state-):
references/state-scalar-fields.mdreferences/state-collections.mdreferences/state-nested-models.mdAuth (auth-):
references/auth-allow-deny.mdreferences/auth-allow-if.mdreferences/auth-built-in-predicates.mdreferences/auth-custom-predicates.mdRPC (rpc-):
references/rpc-refs.mdreferences/rpc-calls.mdreferences/rpc-constructor-calls.mdreferences/rpc-forall.mdScheduling (scheduling-):
references/scheduling-basic.mdreferences/scheduling-recurring.mdTesting (testing-):
references/testing-project-setup.mdreferences/testing-harness.mdreferences/testing-external-context.mdPatterns (patterns-):
references/patterns-error-handling.mdreferences/patterns-idempotency.mdreferences/patterns-common-gotchas.mdtools
Build complete Reboot Web Apps — a Reboot backend behind a standalone browser-facing React frontend, served at a normal URL (not embedded in an MCP host). Layers on top of the python skill for backend mechanics; covers what's specific to standalone Web Apps — no MCP front door, no UI() methods, normal React/Vite SPA scaffolding, and Reboot auth for browser users.
tools
Run an existing Reboot application locally. Detects whether the project is an MCP Chat App or a standalone Web App, makes sure dependencies and secrets are in place, then starts every process the app needs — the backend (`rbt dev run`) and the frontend dev server (for Chat Apps it also opens the setup wizard, from which the user can launch MCPJam on demand). Use this to bring an app back up, e.g. at the start of a new session.
tools
Build complete Reboot AI Chat Apps (MCP Apps) for ChatGPT, Claude, VSCode, Goose, and other MCP hosts. Layers on top of the python skill for backend mechanics; covers what's specific to MCP Chat Apps — the User-type front door, MCP tool exposure, the UI() method type, and the full React/Vite scaffolding.
tools
Build a Reboot application from a user description. Routes to the chat-app skill (MCP Chat Apps for ChatGPT, Claude, VSCode, Goose) or the web-app skill (standalone web apps with a browser frontend). Commits to a route only when the prompt verbatim names the front-door (MCP/Claude/ChatGPT for chat-app; a URL/SPA/"website" for web-app); otherwise asks the user. Does NOT infer the front-door from the app's domain (CRM, todo, dashboard, blog, …) — those describe what the app does, not where it lives.