원클릭으로
workspace
Working across multiple related charms — manifest format, cross-charm relation design, and coordinated Jubilant integration testing
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Working across multiple related charms — manifest format, cross-charm relation design, and coordinated Jubilant integration testing
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Known-good Juju bundle shapes (COS Lite, 12-Factor + COS, Identity Platform, Charmed Kubeflow) — canonical app lists and relation edges
Implementing Juju actions for operational tasks in charms. WHEN: add Juju actions to a charm, implement backup / rotate-credentials / restore actions, declare actions in charmcraft.yaml, write action handlers, test actions with Scenario, run actions with juju run.
Adding and validating charm configuration options. WHEN: add charm configuration options, declare config in charmcraft.yaml, validate config values in config-changed, apply config to a Pebble layer, apply config to a machine charm, write Scenario tests for config.
End-to-end workflow for building ops-framework charms for custom applications (K8s and machine). WHEN: build a custom Juju charm, write src/charm.py with Pebble, write a machine charm with systemd, decide K8s vs machine substrate, scaffold a non-paas-charm with charmcraft init, customise the ops framework charm lifecycle.
Workflow for charming infrastructure software (databases, caches, message brokers, proxies, monitoring). WHEN: charm infrastructure software, charm a database / cache / message broker / proxy / monitoring system, implement peer relations, leader election, primary/replica failover, backup and restore actions, clustering.
Expert guidance for developing, building, testing, and publishing Juju charms using charmcraft. WHEN: edit charmcraft.yaml, run charmcraft pack, run charmcraft init, manage charm libraries, publish a charm to Charmhub, set up multi-base builds, configure relations / config options, set up storage or containers.
| name | workspace |
| description | Working across multiple related charms — manifest format, cross-charm relation design, and coordinated Jubilant integration testing |
Use this skill when the user is building, improving, or operating
more than one related charm at once — for example a producer/
consumer pair for a new relation interface, a microservice suite
where each service has its own charm, or an infrastructure stack
bundled for reuse. Load it alongside per-charm skills (custom-charm,
twelve-factor, infrastructure-charm) rather than instead of them.
For deploying multiple charms into a Juju model, the payoff is a
series of individual juju_deploy + juju_relate calls. Do not
write a new bundle.yaml — bundles are deprecated (see the bundle
skill if the user hands you an existing one).
A workspace is a directory containing a cantrip.workspace.yaml
file that enumerates the charms, their cross-charm relations, and any
shared config. The manifest is optional — the agent works fine
without one — but when the manifest is present, the workspace_info
tool gives you a cheap structured view of the whole layout.
Minimum useful manifest:
workspace: my-platform
description: API tier plus workers, sharing a work-queue interface.
charms:
- name: my-api
path: ./my-api-operator
description: HTTP frontend, 12-factor Flask charm.
- name: my-worker
path: ./my-worker-operator
description: Background worker; scales horizontally.
relations:
- provider: my-api:workers
requirer: my-worker:coordinator
interface: worker-coordination
description: API emits job manifests; worker pulls and acks.
shared_config:
log_level: info
Schema rules (enforced by :mod:cantrip.workspace):
workspace — non-empty name, used as the human-readable label.charms — at least one entry; every entry needs name and path.
Charm names must be unique inside the workspace.relations (optional) — each endpoint uses charm-name:endpoint;
both sides must name a charm listed above. Mark the provider and
the requirer explicitly so the agent knows which side authors the
databag schema.shared_config (optional) — free-form key/value pairs. Use this
for values that should match across charms (log levels, TLS modes,
tenancy identifiers).Paths are resolved relative to the manifest's directory, so check the manifest into the workspace root (alongside the first charm's directory, typically).
Opt in when the user tells you they're working on two or more charms that share code, relations, or deployment lifecycle. Concrete triggers:
Do not add a manifest for a single-charm repo that just happens to have a second directory (for example a docs site) — the manifest should carry work, not paperwork.
Call workspace_info at the start of any conversation that touches
more than one charm. The tool walks upwards from the current working
directory looking for the manifest, so it works whether the user
launches Cantrip from the workspace root or from inside a charm
subdirectory.
workspace_info()
# → Workspace: my-platform
# Charms (2):
# - my-api @ /repos/my-platform/my-api-operator
# - my-worker @ /repos/my-platform/my-worker-operator
# Cross-charm relations (1):
# - my-api:workers → my-worker:coordinator (interface: worker-coordination)
Use the returned charm paths verbatim with the file tools (read_file,
edit_file, grep, glob) — they are already absolute. Each charm
keeps its own charmcraft.yaml, src/, tests/; the workspace is
purely metadata on top.
Two charms integrate through a relation interface. The workspace manifest lists the provider and requirer; your job is to make them agree on the databag schema before you write a line of Python.
worker-coordination, http-api,
shared-secret. This matches Charmhub convention.tls-certificates,
ingress, tracing, prometheus_scrape, …) use the existing
name and its documented databag contract rather than inventing a
new one.get_secret(id=...). Never put plaintext
credentials in relation data.Validate every inbound field with pydantic or a plain dataclass
so a misbehaving partner charm fails loud, not silent.
lib/charms/<provider>/vN/<interface>.py that both sides
import. See the charm-library skill for the authoring flow.MyInterfaceReady, MyInterfaceChanged) instead of observing
raw relation_changed.ops.framework.observe on these higher-level
events; the library hides the databag protocol.Document the contract in the workspace manifest's
relations:[].description so anyone scanning the file sees what the
interface carries without having to read the library.
Integration tests live in exactly one of two places:
Both use Jubilant (see the jubilant-tests skill for the base
patterns). Workspace-level tests add:
import jubilant
def test_api_and_worker_handshake(juju: jubilant.Juju):
# Pack each charm (or rely on a CI step that does it once).
juju.deploy("./my-api-operator/my-api_amd64.charm", app="my-api")
juju.deploy("./my-worker-operator/my-worker_amd64.charm", app="my-worker")
juju.integrate("my-api:workers", "my-worker:coordinator")
juju.wait(apps=["my-api", "my-worker"], status="active")
# Exercise the workflow that uses the relation.
juju.run("my-api/0", "submit-job", {"payload": "hello"})
# Check worker state via its action, its logs, or a query endpoint.
Points worth calling out:
app= set to the workspace name
so the test asserts against the same identifier the user would
use. Different app= values for the same charm kind (e.g. two
workers) reveal scaling bugs in the interface library.juju.integrate(provider_side, requirer_side) is the explicit
form; always pass charm:endpoint so the test documents which
endpoint is which.juju.destroy_model() or pytest fixture in place
if the test leaves state behind — cross-charm suites are large
and clean teardown keeps CI cheap.When the user wants to stand the whole workspace up on a live model, sequence the operations explicitly rather than reaching for a bundle:
juju_add_model (if the target model doesn't exist).charmcraft_pack (or quick_pack) → juju_deploy.juju_relate(app1=provider, app2=requirer).juju_wait blocks until every charm reports active.shared_config, apply each key across
the relevant charms with juju_config (the skill reader knows
which keys belong to which charm).This sequence is deterministic, scriptable, and easy to revert —
all the things bundles aren't. For a reusable deployment, capture
the same sequence in a Terraform module (see the terraform skill).
Cantrip's plan_tasks is still single-charm-scoped; it does not know
how to split work across a workspace automatically. The right pattern
is to:
workspace_info first so you see the charm list.plan_tasks with that specific charm's path as the working
directory so the generated tasks target one charm at a time.relations section or in a shared design note checked in at
the workspace root.This keeps the agent's planning loop honest — one plan, one charm, one set of commits — while the workspace manifest carries the connective tissue.
bundle.yaml to express the workspace.
Bundles are deprecated; use this manifest plus a Terraform module
(or a shell script of individual juju commands) instead. If an
existing bundle is in play, load the bundle skill for the
consumption-only workflow.charm-library skill) or a
PyPI package under charmlibs-* (charm-migration skill has the
modern map).charm-library skill — authoring the relation library that
carries the cross-charm contract.jubilant-tests skill — base patterns for Jubilant integration
tests.terraform skill — long-term replacement for bundle-based
orchestration.bundle skill — consumption workflow for legacy bundles.charmcraft skill — per-charm packaging reference used by every
charm in the workspace.