Packages
A package is a git source (or local directory) that ships
any subset of asset kinds as one versioned unit. Spec
specs/5/28.
Commands
arizuko packages <instance> install github.com/org/pkg # or a local dir
arizuko packages <instance> upgrade pkg # refuses locally-edited assets
arizuko packages <instance> sync # re-apply every package at once
arizuko packages <instance> remove pkg # deletes exactly what it owns
arizuko packages <instance> list
sync is upgrade for every installed package in
one pass — useful after pulling new revisions of several sources. It prints
updated, unchanged or SKIPPED per
package, and a package already at its recorded revision is left completely
alone, so running it twice costs nothing. A hand-edited file is skipped with
a note rather than stopping the run: one local edit should not block every
other package from syncing.
Not the same as the catalog relink generate
does. That one turns a fragment which is still an exact copy of the bundled
catalog into a symlink, so catalog fixes reach you; it never looks at the
installed-package record. sync is about sources and revisions.
Asset kinds
| Kind | File | Applied to |
|---|---|---|
| compose fragment | <name>.yml | services/, up on generate |
| config manifest | <name>.yaml | the rows it names — proxyd_routes, acl, scheduled_tasks, network_rules, web_routes — applied live in one transaction, no restart |
| skills | skills/<name>/ | layered into every group like stock skills |
How it stays safe
- Each install writes an installed-package record (source + resolved revision + owned identities + a per-asset content hash).
upgradecompares the current content hash against the record; a locally edited (dirty) asset is refused, not overwritten.- An install is whole or nothing: the manifest is validated before the first write and its rows land in one transaction with pre-image rollback, so a package whose third grant is bad leaves the first two unwritten.
removedeletes exactly the identities the record owns — rows are withdrawn before the fragment is dropped, and a route or grant you added yourself at another key is untouched.upgradeandsyncrun the same updater, so the refuse-a-dirty-asset rule is identical by construction. They rewrite only the file half of the record; the rows and skills it owns stay recorded, soremovecan still find them.
Blending several products into one group
A package installs against the whole instance. A
product seeds one group — and a group is not
limited to one. Write groups/<folder>/products.toml (the
agent sees it as ~/products.toml), listing the products in the
order they should blend, then apply it:
[[product]]
source = "ant/examples/support" # relative → joined to HOST_APP_DIR
[[product]]
source = "github.com/org/our-brand" # git → cloned and pinned to a revision
arizuko products <instance> apply <folder>
Two products have no common ancestor, so nothing here merges two files' text. Each kind of file gets one rule:
| File | Rule | Yours to edit after? |
|---|---|---|
skills/ | union by name; the last product wins a whole skill | yes — an edit is detected and skipped, never overwritten |
PERSONA.md / SOUL.md | the first product wins; later ones are warned about | yes — written once, never re-written |
CLAUDE.md | one marked region per product, in mix order | yes — anything outside the markers is untouched |
facts/ | union; the same filename twice refuses | yes — written once |
migrations/ | refuses the apply. Nothing runs a product's migrations — there is one instance-wide migration counter and no per-product runner, so seeding them would report success for instructions that never execute. Put setup steps in the product's CLAUDE.md or a skill, which the agent reads | n/a |
eval/*.toml | copied whole — the product's own live tests. See build a product | yes — written once |
tasks.toml | refuses the apply. Nothing reads a product's tasks file — a scheduled task is a scheduled_tasks row created through the schedule_task tool, the dashboard or POST /v1/tasks. A product that wants one says so in its CLAUDE.md and the agent creates it | n/a |
.claude/settings.json | keys merged by name; a key twice refuses. An mcpServers key refuses the apply — no MCP server loads from it, so ship third-party tools as an arizuko connector instead | yes — an edit is skipped, not overwritten |
Dockerfile.ant | at most one in the whole mix | you rebuild the image yourself |
| everything else | copied whole; the first product wins | yes — written once |
That last row is the important one: a file no rule names is copied, not
dropped. Without it a mix would quietly lose every product's
PRODUCT.md.
A collision refuses the whole apply and
writes nothing, rather than picking a winner — if two products both ship
facts/pricing.md, silently keeping one discards the other's
knowledge. Rename one, or drop it from the mix.
Each product in the mix gets its own installed-package record, so
re-applying is safe and cheap: an unchanged mix writes nothing and reports
unchanged. arizuko packages <instance> sync
re-applies every group's mix alongside the instance's packages.
arizuko create --product and
arizuko group … add --product are unchanged — one product,
copied verbatim. Use them when one template is all you need.
Installed packages and blended products are both listed in the operator
dashboard at /dash/packages/; the folder column
is empty for an instance-wide package and names the group for a product.