Skip to main content

Casals logo

Casals

General-purpose canister lifecycle orchestrator for the Internet Computer — built for managed multi-tenant IC deployments with shared upgrade governance.

Casals is fully on-chain: the conductor is a canister that creates, upgrades, snapshots, and rolls back other canisters by calling the IC management canister directly. Sheets, WASM catalog, cycles policy, and audit history all live in Casals' stable state — there is no off-chain worker in the deploy path. The CLI and frontend are thin clients that submit update calls; execution and rollback logic run inside the conductor.

Any project can operate its own Casals conductor to manage a canister fleet. Realms GOS is the reference consumer — it deploys Casals per network and drives rollouts from consumer-side fleet config. Casals also powers provisioning on the gos.earth GOS-as-a-Service platform.

Casals lets a project create, upgrade, roll back, and retire its canisters under that coordinator — organized into sections, stands, and canisters. Governance is pluggable: each section delegates to one or more commanders (principals or external governance canisters). Casals provides the structure and executes approved actions; it never embeds voting logic inside the conductor.

Live demo — https://demo.ic-casals.tech

Design rationale — docs/philosophy (why orchestra / sections / stands / conductor / baton)


Model

Term Meaning
Section A logical group of stands with a shared role (e.g. "Application", "Infra").
Stand A logical unit inside a section — typically one deployed application instance.
Canister An actual canister. Stands contain one or more canisters.
Conductor The Casals orchestrator canister — controller of managed canisters; runs all lifecycle calls on-chain.
Commander A principal authorized by a section or stand to perform scoped lifecycle actions.
Baton / Multisig Optional orchestration canisters (in packages/orchestration/) for managed upgrades and committee approval on target canisters.

Key features

  • Lifecycle — create, chunked install/upgrade, snapshot, module_hash verification, all-or-nothing rollback across a stand.
  • Sheets — declare a whole orchestra in one JSON document (schema); casals up builds it on day one and resumes where a failed run stopped.
  • Canister pool — reuses existing canisters before creating new ones (creation is expensive).
  • Cycles management — native treasury, per-section/stand/canister policy, optional on-chain autopilot, or an off-chain monitor (casals-monitor) that polls balances, runs auto top-ups, and serves the Cycles UI without burning conductor cycles on hourly samplers.
  • Authorized WASMs — ships with hello-world templates (Motoko, Rust, Basilisk, certified-assets frontend) plus orchestration templates (Baton, multisig); more added via governed list.
  • Commanders & permissions — multiple commanders per section/stand; granular permission keys for create, upgrade, subnet whitelist, canister calls, and shell.
  • Shell and browse — optional. When a Basilisk canister is built with its __shell__ / __browse__ endpoints, the Inspect panel offers a shell and a file browser for it; otherwise the panel leaves them out.
  • Frontend — SvelteKit + Internet Identity (8-hour delegation, signed out after 30 minutes idle): Orchestra tree, Commanders, Orchestration consoles, sheet editor, cycles dashboard, WASM catalog, settings. Open the ☰ menu (top-left) for app navigation.

Who can read an orchestra

A conductor answers its reads (get_tree, get_sheet, get_events, cycles, pool, plans) only to its IC controllers, its commanders (each sees the sections and stands it commands) and its enabled monitor. Anyone else gets the version and the orchestra name. The Baton and multisig canisters follow the same rule (a multisig's signer list stays public).

To open an orchestra to everyone, as the public demo does, set it in the sheet:

"environments": {
  "production": { "network": "ic", "public_read": true }
}

casals up carries the setting to the conductor, the multisig and every Baton.


Off-chain cycle monitor

For production deployments, cycle observation and auto top-ups can run in casals-monitor, a hosted service, instead of on-chain timers.

Use the hosted monitor (no account; your conductor's settings are the credential):

  1. In Settings → Cycle operations, choose Off-chain monitor, paste https://service.ic-casals.tech under Hosted monitor service and click Use this service. Casals reads the service's principal from GET /v1/service and fills in Monitor service URL (<base>/v1/<this conductor's canister id>) and Monitor principal.
  2. Save. Casals stores monitor_enabled / monitor_principal / monitor_service_url on-chain, grants the monitor read access to managed canisters (Sync monitor access: status_visibility = allowed_viewers [monitor]), then calls POST /v1/instances on the service, which verifies those settings and starts polling. The Hosted monitor status card shows state (active / consent revoked / unreachable), last poll and cadence; Register / check status re-runs the registration.
  3. To leave, switch back to On-chain (or change the principal) and save: the service stops auto top-ups at its next pass and disables the instance after 24 h; the next Sync monitor access drops the viewer grant. If the sheet declares a monitor block (below), remove it too: a casals up that stores a changed sheet switches the monitor back on.

What the monitor can and cannot do. Its principal is an allowed viewer, never a controller: it can read canister_status, ask the conductor to top_up orchestra canisters (the conductor ignores the requested amount and deposits exactly what its own cycle policy says — zero when the canister is not below policy, never below treasury_reserve) and trigger convert_treasury_icp (ICP already on the treasury → cycles on the same treasury, at most once per 10 minutes). It cannot install, stop, destroy or reconfigure anything, and set_settings refuses to list it as an extra controller. New canisters get the viewer grant at provisioning, while Casals still controls them; canisters already handed to a baton are reported as skipped: not a controller by sync_controllers; casals up --sync-monitor grants those too (below). The conductor's own SPA asset canister is read by the monitor only if its viewer list is set by the multisig (CallCanister → update_settings).

A self-hosted monitor works the same way; its host must be added to connect-src in frontend/static/.ic-assets.json5, or you fill the two fields by hand.

Or declare it in the sheet, so a fresh orchestra is watched from its first canister:

"environments": {
  "production": {
    "monitor": {
      "principal": "wdtda-uafrj-6down-pxxwi-emhpc-uivwv-zdi3c-szrq7-hxodh-wlvas-iqe",
      "url": "https://service.ic-casals.tech"
    }
  }
}

url is the service's base URL; the conductor appends /v1/<its canister id>. casals up hands the block to set_sheet, which switches the conductor to off-chain mode before plan/apply creates anything, so every canister is provisioned with the viewer grant. The block's URL and principal are a first default: once the conductor has a service URL (from an earlier deploy or from Settings) it keeps that one, so change them in Settings. When the block changes, or with casals up --sync-monitor, up also grants it on canisters the conductor no longer controls: it lends the conductor control of each one (as deployer, or through the multisig), runs sync_controllers, and puts the original controllers back. The canisters the deployer cannot reach are listed under monitor_access.unreachable. Registering with the service is still Register / check status in Settings (or POST /v1/instances).

This disables on-chain balance sampling and autopilot on the conductor (cycles_sampling: false, cycles_autopilot: false) while the monitor paymaster tops up from the same Casals treasury. Each signed-in user can save a Notification email under Settings → Your settings. The monitor sends operational notices (treasury cannot fund a top-up, monitor consent withdrawn, and later notices of the same kind) to those addresses. A legacy orchestra-wide address, if one was saved before this split, is included too.

For scripted wiring, see scripts/examples/wire_monitor.py (JSON config with monitor_url, monitor_principal, casals_backend, casals_frontend).


Toolchain

  • icp-cli for build & deploy (icp.yaml); dfx is not used.
  • Basilisk + ic-basilisk-toolkit for the backend.
  • casals-store — the WASM store: a certified-assets canister (chunked batch upload, on-chain sha256, pinned directories) that casals up creates and seeds; every install streams from it — and every frontend asset bundle (docs/BUNDLES.md). Upload from the CLI (casals up) or from the browser on /files.

Quick start

Needs Python 3.10+ and icp-cli. No checkout of this repo.

pip install ic-casals
casals init hello-world
casals up hello-world --yes --local

casals init hello-world adds that orchestra to casals.json: a Basilisk backend and a hello-world frontend, plus the conductor that runs them, installed from the Casals GitHub release that matches the CLI (--release <tag> picks another). casals up <name> applies that orchestra from casals.json. A path to a sheet file still works. --local creates the local-dev identity, starts a local network and funds the identity. Outside an icp project, the network gets its own project under ~/.casals/replica.

casals up prints a short step log ([1/7], a clock time, and the address to open). The full log of that run is a file under ~/.casals/logs (or $CASALS_HOME/logs). --verbose prints the full log on the terminal as well. --json prints the machine-readable result on stdout.

In a checkout of this repo, the corpus sheets build the conductor from source instead (pip install -e . -r requirements-dev.txt, plus make and Node/npm for the UI):

casals up tests/e2e/orchestras/minimal/casals.json --yes --local

Without --local, create the identity before the network starts: a local network funds only the identities that exist when it starts.

icp identity new local-dev --storage plaintext
icp network start -d
casals -e local --identity local-dev up tests/e2e/orchestras/minimal/casals.json --yes

casals up <sheet> is the day-one deploy path: it validates the sheet, builds and deploys the conductor and the casals-store store, uploads the referenced WASMs into it, and builds what the sheet declares (set_sheet → plan → apply). From then on the orchestra is operated imperatively — the UI, casals upgrade, create_stand, upgrade_to, … — with no on-chain reconciliation loop (issue #52).

On mainnet, casals up stops when it finds no bindings for the environment (under ~/.casals, or $CASALS_HOME) rather than create a second conductor. Point it at the live one with --conductor <casals-backend id>, or pass --bootstrap for a genuine first deploy.

Why the sheet stops being the truth after day one

Casals is deliberately not a reconciler. Terraform and Kubernetes converge the world onto a desired-state document, which works because the reconciler holds complete authority over what it manages, convergence is therefore always reachable, and any difference between file and world is drift to be erased. An orchestra under shared governance satisfies none of those:

  • Authority is on-chain and shared; a file has none of it. A signer added by multisig proposal, a commander who redeemed an access code, a baton that ran an upgrade — all legitimate, none of them in your document. A diff between sheet and chain is genuinely ambiguous: it may mean "someone should approve this" or "the file is stale", and no control loop can tell which.
  • Convergence needs other people. An item that requires N-of-M approval may wait days or never pass. A loop that treats that as failure blocks or nags forever; the right behaviour is to file a proposal and stop.
  • The orchestra is itself an actor. Stands minted at runtime, a tenant's instance growing a canister (sync: manual, issue #51) — not drift, the system working. This holds even with a single controller, so it is not only a consequence of decentralized control.

So the chain is the source of truth. The conductor records each release into its stored document, casals export regenerates a sheet from what is live, and casals plan / casals oracle report divergence rather than erase it. After hand-off the tooling may read, ship artifacts the conductor authorizes, and file proposals — it must never assert the file over the chain. Keeping a sheet hand-edited and re-running up on a governed orchestra is the one way to get this wrong: it will plan to undo governance changes it knows nothing about.

The full argument is the genesis document slide in docs/philosophy.

Open http://casals_frontend.local.localhost:8000/ — log in with Internet Identity using a principal listed on Commanders (or a Casals controller).

Shipping this conductor

casals up is day one. A later change to this repo's backend or UI is scripts/deploy.sh: it rebuilds what you name and ships it with a delegated session identity. The production YubiKey signs that session once (docs/OPERATIONS.md); the deploy itself does not touch the key. With no --identity and no $CASALS_IDENTITY, the script uses the prod-session* delegation that expires last and skips an expired one.

scripts/deploy.sh                         # backend and frontend
scripts/deploy.sh frontend                # Casals UI only
scripts/deploy.sh backend                 # conductor wasm only
scripts/deploy.sh --skip-build both       # wasm and dist/ already built
scripts/deploy.sh --identity prod-session frontend

The UI is uploaded to the wasm store and copied onto the Casals frontend in batches. The backend is one upgrade of the conductor wasm. While mainnet calls are in flight, one line shows how much of that work is done and an ETA from the pace so far:

[ 42%] 27/65 sync casals-frontend (27/65) · ETA 4m 12s

A backend install is a single call, so its slice of the bar moves when the call returns. The footer’s first timestamp is the git commit time; deployed … is the wall clock of the frontend build that was shipped.

Contributing: AGENTS.md is the contributor guide (layout, tests, known quirks).


CLI

Install the casals command:

pip install ic-casals

Run from your project directory (where casals.json lives):

casals status                                      # version + object counts
casals tree                                        # Section → Stand → Canister tree
casals events                                      # audit log
casals wasms                                       # authorized WASM catalog
casals bundle dist/ -o app-1.2.0.tgz               # pack a frontend build into a hashed bundle
casals init hello-world                           # add the hello-world orchestra to casals.json
casals up hello-world --yes --local                # day one, local replica
casals upgrade sheet.json --wasm my-backend        # release: move every canister running that family to the new build
casals upgrade sheet.json --content my-frontend    # release: every frontend with that content serves the store's bundle
casals cycles                                      # treasury + per-canister balances
casals pool                                        # canister pool
casals export sheet.json                           # the sheet the conductor was built from + bindings
casals oracle sheet.json                           # check the live orchestra against the sheet

casals -e production --identity my-key status      # a mainnet environment of the sheet, explicit identity

-e names an environment of the sheet; its network says where it runs. Read commands (status, tree, events, wasms, cycles, pool, export) print JSON. up, plan, upgrade and oracle print a step log or a table; --json gives the machine-readable result instead. Errors go to stderr as {"ok": false, "error": "..."} with exit code 1.

Without installing, the same commands are available via:

python3 scripts/casals.py status
make cli ARGS="status"

API

JSON-in / JSON-out text endpoints. Returns {"ok": true, …} or {"ok": false, "error": "…"}.

Kind Method Purpose
query get_tree full Section→Stand→Canister tree
query get_sheet / list_pool stored day-one sheet + canister pool
query get_cycle_history balance samples over time
query list_permissions assignable commander permission keys
query list_backend_controllers Casals canister IC controllers (for Commanders UI)
update create_section / create_stand / create_canister structure
update propose_upgrade / sync_content / deploy_content imperative release of a baton-governed member / a frontend bundle (one round / all rounds)
update set_commander / set_permissions commander principals + permission grants
update upgrade_to stand/canister upgrade with snapshot rollback
update add_authorized_wasm / remove_authorized_wasm WASM catalog
update top_up / reconcile / set_cycle_policy cycles management
update sync_controllers sync monitor read access (status_visibility allowed viewer) on managed canisters

Full endpoint list: casals_backend.did.


About the name

Named after Pablo Casals — cellist and conductor. This project coordinates canisters the way a conductor coordinates an orchestra.


Disclaimer

This software is not production-ready. Do not deploy to mainnet or use with real canisters, cycles, or governance authority you cannot afford to lose.

Casals is in early development (alpha). It may contain bugs, breaking changes, and unknown security vulnerabilities. It has not undergone an independent security audit. Use at your own risk.

  • Not recommended for production deployments on the Internet Computer
  • No guarantee of correctness, availability, or security
  • APIs and behavior may change without notice

License

MIT — see LICENSE.

Metadata

Release files for ic-casals 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ic-casals 0.6.0
File Size Uploaded
ic_casals-0.6.0.tar.gz 126.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ic-casals 0.6.0
File Interpreter ABI Platform
ic_casals-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 259.9 kB

Release files / ic_casals-0.6.0.tar.gz

Download URL ic_casals-0.6.0.tar.gz
Size 126.2 kB
Tags Source
SHA-256 checksum
How to use checksums
79bc9a7f336e43b4fd890bf76c7053477ccc157f2896bcaf727a6d8b459bd6de
BLAKE2b-256 checksum
How to use checksums
08fe5567e51cf20b9721e0f4b164751fb95175475131cf06f6486d727a2612b2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release files / ic_casals-0.6.0-py3-none-any.whl

Download URL ic_casals-0.6.0-py3-none-any.whl
Size 133.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
141a2c933807fd2d7724591f3b21cdb7e814e232c54942cab27b854cfd96ba38
BLAKE2b-256 checksum
How to use checksums
d7071c10ad77d52719ed1347be5989d39e8486da21223131c0ea88e3ea731ec9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page