Connect to Pyrula Cloud
The scaffold runs offline against a scripted model, so no provider key. This page uses
Python 3.11 to 3.15 and a POSIX shell, in an empty directory. Stop pyrula dev with Ctrl+C.
1. Scaffold, connect and run
Section titled “1. Scaffold, connect and run”In Cloud, open Setup for your project and environment and copy the key. A key is for one environment; its runs appear only there.
A development or staging key sends prompts, replies and tool traffic (capture level io);
a production key keeps them on your runtime (lifecycle). To change that, see
What leaves your runtime.
pip install "pyrula[agents,cloud]"pyrula initexport PYRULA_CLOUD_API_KEY="<key>"pyrula dev app.pypyrula init prints wrote app.py. pyrula dev runs the agent once and prints a link to the run:
Target: app.py:supportUsing SQLite store .pyrula/dev.db - runs survive a restart; --fresh starts emptyCloud: connecting to <origin>Cloud: connected to <origin> as runtime <runtime-id> (project <project-id>, environment <environment>) support submitted <run-id> tool lookup_order completed - Order A-1042 has shipped. Total $42.00.Cloud: run <run-id> -> <link>2. When it does not connect
Section titled “2. When it does not connect”If Cloud is unreachable or refuses the key, pyrula dev keeps running locally and prints one
line saying why. Pyrula retries an unreachable Cloud, or one answering 500 or 429, with backoff.
Runs that finish meanwhile are held in memory (bounded by PYRULA_CLOUD_BUFFER_MAX) and sent
once it connects; a process that stops first drops them and logs cloud_held_discarded_at_stop.
To check setup without starting anything:
pyrula dev --checkIt never registers a runtime and exits 1 if a check failed; fixes are in
Troubleshooting. With no key, pyrula dev prints
Cloud: off (no PYRULA_CLOUD_API_KEY) - running locally only.
3. Next
Section titled “3. Next”In your own code
Section titled “In your own code”For an existing runtime, install "pyrula[cloud]" and set the key where your workers start:
pip install "pyrula[cloud]"Keys look like pyr_<env>_... with <env> of live, test or dev. A malformed key stops
start-up with a ConfigurationError naming PYRULA_CLOUD_API_KEY. With
PYRULA_CLOUD_CONTROL=off the key is not read.
Settings
Section titled “Settings”| Variable | Effect |
|---|---|
PYRULA_CLOUD_URL | Cloud to connect to. Defaults to https://api.pyrula.io; set it for self-hosting. |
PYRULA_CLOUD_CONTROL | Unset, derived or full: report and accept cloud commands. telemetry: report only. off: no connector is built. Other values stop start-up. |
PYRULA_RUNTIME_NAME | Label for this runtime in the UI. Defaults to a generated name. |
PYRULA_RUNTIME_ID | Stable identity across restarts. Defaults to a fresh id per process. |
PYRULA_CLOUD_CAPTURE | Capture level cap, see What leaves your runtime. |
PYRULA_CLOUD_ALLOWED_RUNNERS | Comma-separated workflow/agent names Cloud may act on; control for any other name is refused. Unset allows all. |
PYRULA_CLOUD_OUTBOX_PATH | Spool location, or off to disable. |
PYRULA_CLOUD_OUTBOX_SYNC | full fsyncs every spool write. |
PYRULA_CLOUD_OUTBOX_MAX_AGE_S | How long an unacked spooled event is kept. Default 7 days. |
PYRULA_CLOUD_CLAIM_GATE_SECONDS | Wait for the first store-binding verdict before a polling worker takes work. Default 10; 0 disables. See below. |
PYRULA_CLOUD_DRAIN_DEADLINE_S | Most time a shutdown spends sending buffered events. Default 5. |
PYRULA_REDACT_KEYS | Extra field names to redact (redaction policy). |
Not losing events
Section titled “Not losing events”Critical events (run lifecycle, steps, tool and usage results, terminals) go to a local spool, the outbox, before they’re sent, and are deleted once Cloud accepts them. Token deltas and other live-stream events aren’t spooled; when one is dropped, Cloud is told where the gap is.
The spool is on by default at $XDG_STATE_HOME/pyrula/ (~/.local/state/pyrula/), one file
per project.
- Containers: put
PYRULA_CLOUD_OUTBOX_PATHon a persistent volume, or the spool dies with the container. - Several projects on one host: never share one
PYRULA_CLOUD_OUTBOX_PATH. Rows are separated only by environment name, so one project’sproductionkey would send another’sproductionrows. - Power loss: writes reach the OS, not the disk (~29 µs per event). Set
PYRULA_CLOUD_OUTBOX_SYNC=fullto fsync each event. - A damaged spool: an unreadable file never stops your app from starting. Pyrula renames it
to
<spool>.corrupt-<UTC yyyymmddTHHMMSSZ>, logs oneoutbox_quarantinedwarning and starts a fresh spool; the newest three moved files are kept. A spool written by a newer SDK stops the connector; upgrade. A spool held open by another live process is left alone, and one it cannot read or write (another user’s state directory, a read-only mount) logs oneoutbox_unreadablewarning and runs without a spool. - Opting out:
PYRULA_CLOUD_OUTBOX_PATH=offkeeps delivery to the life of the process, with no backpressure.
Outbox contents
Section titled “Outbox contents”- Events already redacted for your capture level, as plaintext SQLite. File
0600, directory0700. - A row stays until Cloud accepts it or
PYRULA_CLOUD_OUTBOX_MAX_AGE_Spasses; an expired row counts as dropped and is reported. - A restarted runtime sends the rows a killed process left for the same project and
environment. This relies on
flock; on Windows or a filesystem without it, those rows age out instead. - A row is only sent while this runtime matches the store binding current when it was written.
- Deleting the file while stopped discards the backlog.
- Keys without a project id get a file named for a digest of the key; rotating the key starts a new file.
One store per project and environment
Section titled “One store per project and environment”A run store belongs to one project and environment. The first runtime to register against an unbound store binds it. Bind a production store first, so a laptop or CI job holding a production key can’t do it by accident:
pyrula store binding showpyrula store binding set --project P --environment Epyrula store binding clearclear warns that the store’s history will go to whichever environment binds next. Stop that
store’s workers first.
Give each environment’s workers their own store: a separate Valkey, a separate database index
on a standalone server, or a key namespace (PYRULA_VALKEY_NAMESPACE or --namespace), the
only option on a cluster. Unset means the namespace "default".
- A Valkey ACL limiting a runtime to
~<namespace>:*keeps it out of other namespaces. The store needs+eval +evalsha +script|loadand sends noFUNCTIONcommand (recipe). - A worker whose key belongs to another project or environment runs nothing from the store,
reports nothing and accepts no control. It logs an ERROR, reports not ready on
/readyz, and the Workers page shows a notice. A mismatched process can still submit runs; the store’s matched workers run them. - While the store is unbound and Cloud has not answered register, a polling worker waits up
to
PYRULA_CLOUD_CLAIM_GATE_SECONDSbefore taking work. A store already bound to the key’s project skips the wait. - Run Valkey with
maxmemory-policy noeviction: an evicted binding unbinds the store, and the next runtime to check binds it again.
When Cloud stops accepting
Section titled “When Cloud stops accepting”| Variable | Default | Effect |
|---|---|---|
PYRULA_CLOUD_OUTBOX_HIGH_WATER_BYTES | 256 MB | Above this, submit() raises BackpressureRejected. Claimed work keeps running and drains the backlog. |
PYRULA_CLOUD_OUTBOX_HARD_CAP_BYTES | 2x high water | Above this, the spool refuses new events and claiming stops. |
Only an ack or the age limit deletes rows. The heartbeat reports egress_pressure (ok,
high_water or saturated). Cloud trails by a fraction of a second under healthy load (median
0.21 s for run_start) and by seconds or minutes after a burst or outage.
Stopping
Section titled “Stopping”Shutdown drains the queue up to PYRULA_CLOUD_DRAIN_DEADLINE_S. With a spool, critical events
left behind go out on the next start and only stream detail is lost; without one, undelivered
critical events are lost too. The runtime logs the counts at exit:
cloud_drain_timeout stream_discarded=N critical_undelivered=M spool_enabled=SIf nothing appears
Section titled “If nothing appears”- Key set but
pyrula-cloud-clientnot installed: start-up raises a configuration error. Install it or unset the key. - No error, nothing in the UI: the key probably isn’t reaching the process (an unexported variable, or a container without the environment). Check where the runtime runs:
python -c "import os; print(bool(os.environ.get('PYRULA_CLOUD_API_KEY')))"