Workspaces and Dependencies
A workspace groups several related packages under one root with a shared lockfile and a shared dependency-version pool. Use one when you have packages that need to evolve together — a domain ontology and the test suite that exercises it, for example, or a foundational vocabulary and the domain packages that consume it.
This chapter covers workspaces and the lockfile + registry mechanics that hang off them.
A workspace manifest
The workspace root has its own ox.toml, with a [workspace] section in place of (or alongside) [package]:
[workspace]
members = ["packages/lease-tutorial", "packages/lease-tests"]
resolver = "1"
[workspace.package]
edition = "2025"
license = "CC-BY-4.0"
authors = ["Lease Team <leases@example.com>"]
[workspace.dependencies]
ufo = "0.2.1"
cofris = "0.2.1"
Three sections:
[workspace]— declares the workspace and lists members.membersis a list of relative paths to package directories (glob patterns allowed); each must contain its ownox.toml.[workspace.package]— defaults that members can inherit. Common fields:edition,license,authors,repository,description.[workspace.dependencies]— version pinnings that members can inherit via<dep>.workspace = true.
Each member’s ox.toml carries both the [project] perspectival-composition layer and the [package] registry layer (see Chapter 1.3 for why both):
[project]
name = "lease-tutorial"
version = "0.1.0"
edition = "2025"
entry = "root.ar"
[schema]
root = "src"
[package]
name = "lease-tutorial"
version = "0.1.0"
edition.workspace = true
license.workspace = true
[dependencies]
ufo.workspace = true
cofris.workspace = true
workspace = true says “use the workspace root’s value.” This keeps version pins consistent across members without the rote of repeating them per file.
How resolution works
When you run ox install at the workspace root:
- The compiler walks each member’s
[dependencies], expandingworkspace = trueto the workspace root’s pin. - It builds the unified dependency graph across all members.
- External deps that appear in multiple members resolve to the same version — Cargo’s pattern. If two members declare
ufowith incompatible version constraints, the resolution fails up front. - Workspace members always resolve to the local sibling, never to a registry version. So if
lease-testsdepends onlease-tutorialandlease-tutorialis a workspace member,lease-testssees the local source, not a published version. - The lockfile lands at the workspace root (
ox.lock) and is shared across members.
The lockfile
ox.lock records the exact resolution. It is bivalent: each entry carries two identifiers:
- A content hash — the byte hash of the package’s tarball as published. Reproducible-byte verification at install.
- A Merkle root over construct signatures — a hash over the package’s exported items’ shapes (concept names, field types, rule signatures). Semantic identity: two packages with the same Merkle root expose the same surface, even if their byte hashes differ.
The bivalence matters because Argon packages can be republished with cosmetic changes (a comment fix, a doc-string rewrite) that change the content hash but not the Merkle root. Tooling that wants “did the API change” looks at the Merkle root; tooling that wants “did the artifact change” looks at the content hash.
The lockfile is checked into version control — it is the source of truth for “what versions did this build use.” Re-running ox install against an existing lockfile is deterministic.
Updating dependencies
Two commands:
$ ox update # update all packages within their semver constraints
$ ox update <package> # update a specific package
Both write a new ox.lock. Both report a compatibility diff: which package versions changed, and (via the Merkle root) which API surfaces changed. A surface-changing update is a yellow flag worth reviewing before committing.
The registry
ox publish ships a package to the registry:
$ ox publish
Building canonical tarball
Computing content hash + Merkle root
Authenticating against registry
Uploading tarball
✓ Published lease-tutorial v0.1.0
The registry’s URL and authentication are configured in ~/.argon/settings.toml; for the Sharpe-internal argon-packages registry, authentication uses GitHub tokens.
ox audit re-verifies the lockfile’s hashes against the cached packages:
$ ox audit
✓ All package content hashes match the lockfile.
✓ All Merkle roots match the lockfile.
The verification surfaces tampering or registry corruption. Run it in CI if you need a strong guarantee that the build’s inputs have not been substituted.
Local-path dependencies
For dependencies under active development, point at a local directory:
[dependencies]
local-pkg = { path = "../local-pkg" }
The compiler treats local-path deps as workspace members for resolution — they resolve to the source on disk, not a registry version. Use this during development; switch to a version pin before release.
Edge cases worth knowing
- Single-package workspace is fine. A package with
[package]only is a workspace of one. You do not need a separate[workspace]table to use a workspace pattern; many small packages live this way. - Members cannot have
[workspace]. A member’sox.tomlcarries[project]+[package](and optionally[dependencies]); the workspace declaration belongs at the root. - The lockfile lives at the workspace root, not per member. Members do not have their own
ox.lock. target/is per-workspace. Build artifacts and test caches share a directory at the workspace root, so members do not duplicate work.- Git deps are policy-controlled. The Sharpe-internal default disallows git-source dependencies (only registry + path are admitted). The mechanism exists in the manifest grammar for future use.
Putting it in the running example
We have built lease-tutorial as a single-package workspace so far. To grow into a multi-member layout, restructure:
lease-workspace/
├── ox.toml
├── ox.lock
└── packages/
├── lease-tutorial/
│ ├── ox.toml
│ └── src/
│ ├── root.ar
│ ├── prelude.ar
│ ├── metatypes.ar
│ └── ...
└── lease-extras/
├── ox.toml
└── src/
└── ...
Workspace ox.toml:
[workspace]
members = ["packages/lease-tutorial", "packages/lease-extras"]
[workspace.package]
edition = "2025"
license = "CC-BY-4.0"
[workspace.dependencies]
# none yet
Each member’s ox.toml:
[project]
name = "lease-tutorial"
version = "0.1.0"
edition = "2025"
entry = "root.ar"
[schema]
root = "src"
[package]
name = "lease-tutorial"
version = "0.1.0"
edition.workspace = true
license.workspace = true
[dependencies]
Type-check from the workspace root:
$ ox check
ox lease-tutorial v0.1.0 (./packages/lease-tutorial/ox.toml)
ox lease-extras v0.1.0 (./packages/lease-extras/ox.toml)
check passed
The workspace pattern scales — a real ontology project may have ten or twenty members, with foundational packages (UFO, BFO), domain packages (lease, payments, regulatory), and a test workspace member coordinating them.
Manifest patterns in the catalog
The _catalog/manifest-*/ workspace family exercises each manifest section in isolation:
_catalog/manifest-catalog/{minimal,multi-version,registry-override,negative-missing-dep,idiom}/—[catalog]declarations across single- and multi-version slot configurations, plus a registry-override variant that points one dep at a private registry._catalog/manifest-tree-shaking/{minimal,with-conditional-exports,with-feature-flags,cross-package,negative-orphan-import}/— the[tree-shaking]directives that prune unused items at canonicalization time._catalog/manifest-defeat/{minimal,prioritized-ordering,plugin-strategy,cross-package,negative-missing-strategy}/—[defeat]section configurations driving the defeasible-rule ordering (foundation: Chapter 5.2)._catalog/manifest-modules/{minimal,nested-lattice,cross-package,mixed-world,negative-cycle}/— the[modules]+[standpoints]machinery covered in Chapter 5.4.
Each variant ships a runnable ox check (and, for negative variants, the expected diagnostic code). Read them as a reference when wiring up a new workspace.
Summary
A workspace groups packages under one root with a shared lockfile and shared version pool. Members inherit defaults from [workspace.package]; they pin versions through [workspace.dependencies]. The lockfile is bivalent — content hash plus Merkle root — so tooling can distinguish byte and semantic identity. The registry handles publish/fetch/audit. Local-path deps support development. Single-package workspaces are fine; the pattern scales when you need it.