Beta·every release is a pre-releaseAPIs and harness behavior are still changing·every release until v1 is a pre-release
uzev1.0.0-beta.12
Reference

agents.yaml

The file a project writes, the file uze derives from it, and what each one is allowed to say

Two files at the project root, both committed, with one rule between them:

FileWritten byHolds
agents.yamlyouWhat the project declares: which marketplaces it draws from, which plugins it wants, and, under workspace:, how its agents work
agents.lockuzeWhat resolving those declarations produced: the commit each marketplace was read at, and a digest of the bytes that landed

The lock carries no intent, so deleting it loses nothing: uze install regenerates it. Deleting agents.yaml loses the project's decisions.

git clone && uze install reconstructs the environment from the pair alone.

agents.yaml

Two kinds of key, one per module. The package manager's sit at the root; the workspace's live under one workspace: section, which a project that only installs plugins never writes:

marketplaces:
  ai:
    git: https://github.com/you/plugins
    ref: main
    plugins:
      - review
      - changelog

workspace:
  worktree: always
  delivery: pr

Created by uze install or uze <plugin>@<market>, explicit acts of setting a project up, with the package manager's vocabulary written out and commented. Nothing about the workspace is written, not even commented: the section appears when you choose something in the workspace or write it yourself. Nothing that merely reads a project creates the file.

Every key is optional; a project declaring only a workspace policy is as valid as one declaring only plugins. Unknown keys are an error, not silence: a typo in a file a person wrote should not be read as a decision. Writing revision: here is refused by name: that belongs to the lock.

This document is patched in place, so a comment you wrote beside an unrelated entry survives uze <plugin>@<market>.

marketplaces

Every marketplace the project draws from, keyed by the name you refer to it by, with what it takes from each. Exactly one source per marketplace:

marketplaces:
  ours:
    git: https://github.com/acme/plugins
    ref: main               # optional: branch, tag or sha; also the pin that moves
    subdirectory: market    # optional
    plugins: [review, changelog]
  local:
    path: ../marketplace
    plugins: [bench-runner]

Plugins are bare names: the source above says where they come from, and ref: is what moves them.

A marketplace is a Git repository, whether you spell it as a URL or as a directory on this machine. Not because Git is how the bytes travel (a directory does that), but because a commit is the only thing that answers are these the bytes I installed? and is there something newer? A path: is therefore where this machine reads the repository; what the lock records is its remote and a commit, which is what another machine can reproduce.

The lock records every Git source in one spelling: git@github.com:acme/plugins.git, ssh://git@github.com/acme/plugins and https://github.com/acme/plugins.git all become https://github.com/acme/plugins. That is a name, not a transport: each machine reaches it anonymously when it is public, and with its own credential helper or SSH key when it is private, so nothing in these files says how to authenticate, and a collaborator with no key installs a public marketplace as easily as you do. A marketplace you develop is read from your checkout with uze market link, which touches neither file.

The official marketplace is not declared here. uze-official is built into the binary, its plugins are installed for every project, and an entry claiming that name is refused: the only thing it could express is shadowing the built-in one.

workspace

How uze workspace runs the agents it launches, and where the project keeps the artifacts that describe it. One flat section. The layout of the worktrees themselves (.worktrees/<id>, branch agent/<id>) is fixed infrastructure and is deliberately not declared here.

The section is the workspace's. The package manager carries it without reading it, so a mistake in it never fails uze install or uze status; the workspace reports it.

KeyDefaultMeaning
worktreemanualWhen an agent launched here gets a worktree: always, at launch, or manual, only when you move it with To worktree. Until then it works in the primary checkout, on your branch
deliveryhandoffWhat happens to finished work: handoff (leave the branch), merge (fast-forward the target), pr (push and open a request)
targetthe branch the primary checkout is on when the task is createdThe branch finished work targets
branchagentThe vocabulary an agent's own name for its work is judged against: a preset (conventional, gitflow, flat, agent) or your own list of types. agent names no work
linknoneIgnored files a fresh worktree links from the primary checkout, like .env
setupnonePrepares a worktree, run in it after linking. A failure warns; it never blocks a launch
gatenoneRun in the task's worktree on the rebased commits. A non-zero exit refuses delivery
slotspeak concurrencyThe most worktrees that may exist at once
spare2Free worktrees kept warm for the next agents, the most recently used first. Every other free one is removed, its branch kept
idle_days3Days a free worktree may sit unused before it is removed too
artifactsnoneWhere the project keeps what describes it; see below

Every key but artifacts is about a worktree, so none of them does anything for an agent working in the primary checkout until it is moved into one.

setup and gate each take one command or a list run in order. Use the list when a failure should say which step failed:

workspace:
  worktree: always
  delivery: pr
  target: main
  branch: conventional
  link: [.env, .env.local]
  setup: pnpm install
  gate:
    - pnpm test
    - pnpm lint
  slots: 3
  spare: 2
  idle_days: 3
  artifacts: docs

A command is a line for a POSIX shell, the sh Linux and macOS run it in. For a project worked on from Windows too, spell a step for each shell instead: Windows runs its windows spelling in Windows PowerShell. A command both shells read the same way is still written twice, because each shell is only ever handed the line written for it:

workspace:
  setup:
    posix: cp .env.example .env
    windows: Copy-Item .env.example .env
  gate:
    - posix: pnpm test
      windows: pnpm test
    - posix: test -f dist/app.js
      windows: if (-not (Test-Path dist/app.js)) { exit 1 }

A step with no spelling for the machine it runs on is not run there. A setup step is skipped with a warning naming it; a gate step refuses the delivery, because work nothing checked never lands. uze status lists every such step.

A link entry must be relative, stay inside the repository, and be ignored by it: a tracked file linked into a worktree would land in the agent's commits as a symlink. All three are checked when the file is read, not silently dropped. The agent writes through a link into your primary checkout, so only what it reads belongs there.

The policy lives here rather than in machine state because uze install projects it into the tracked AGENTS.md: anything resolved outside the project would rewrite a shared file to whichever machine ran the command. A worktree's own manifest is ignored for the same reason: the primary checkout owns the policy.

artifacts

Where the project keeps the artifacts that describe it: one directory, or a list of them, each read as deep as it goes.

workspace:
  artifacts: docs
  # or several:
  # artifacts: [docs, design]

It names places, never kinds. What a file is, it says by its own content, and each surface takes the files it recognizes and ignores the rest: the architect surface draws the Mermaid files it finds there, and the spec surface lists the decision records. A kind of artifact uze learns later needs no new key.

An entry that climbs out of the project (../) or starts at the machine's root refuses the whole declaration, naming the entry, because this file is checked in and would point somewhere else on every machine.

Nothing lists the files. Each one says what it is in its own first word, which is also the area it appears under, and names itself with Mermaid's own front matter, falling back to its file name:

---
title: System context
---
C4Context
  Person(dev, "Developer")
  System(uze, "uze")
  Rel(dev, uze, "Installs, launches, reviews")
The diagram starts withArea
C4Context, C4Container, C4Component, …C4
sequenceDiagramSequence
flowchart, graphFlowchart

Levels, and the way down to the code

C4 is a model of levels (a system, the containers inside it, the components inside one of those), and the files are joined into levels the same way they are sorted into areas: by what they already say. A box has a level below it when another C4 file draws its inside, which is a boundary carrying the box's alias:

system-context.mmd    System(uze, "uze", …)
containers.mmd        System_Boundary(uze, "uze") { … Container(core, "Core", …) … }
core-components.mmd   Container_Boundary(core, "Core") { … }

A box like that is marked, and entering it opens the file that draws its inside; the way in is kept as a trail, every step of which goes back. The last level is the code itself. Mermaid's own $link on an element names where it lives, relative to the project, and entering that box opens it in the code surface:

Component(store, "store", "Rust module", "Owns installed bytes", $link="crates/core/src/store.rs")

A flowchart says the same with click store href "crates/core/src/store.rs".

Within an area the files are listed by name, except C4, which is listed the way it is read: the context, then containers, then components, then the dynamic and deployment views.

A .mmd or .mermaid file of any other kind is still listed, under Other, with the reason it was not drawn: a file that silently fails to appear looks like a lost file.

Everything here is a file somebody wrote. What the project is (where its lines are and which of them are hot) is measured rather than written, and is the code surface's map: press m beside the file tree, or its Map chip.

agents.lock

Generated, never edited, and deleted outright when nothing is left to reproduce. It is spelled the way agents.yaml spells things, so a reader moving between the two files does not learn the same vocabulary twice:

version: 1
marketplaces:
  ai:
    git: git@github.com:you/ai.git
    revision: 02d2c9831eb619821c574622a78bbc5f6189485a
plugins:
  git:
    marketplace: ai
    integrity: sha256:f429f157f7cec03d702f1749416ea59174b9af79c94fc281756467b0cb6b81cd

Resolution adds exactly two things to a declaration:

  • revision: the immutable commit the marketplace was read at. Non-optional beside git, so "a marketplace with no pin" cannot be written down. Reproduction reads this, never the declared ref:: a lock that re-resolved main would install whatever had been pushed since.
  • integrity: SHA-256 over the bytes the Store ingested, verified on install before ingest and before any harness sees anything. A mismatch names both digests and installs nothing. A path: marketplace records none: a digest of a directory somebody is editing would be wrong by the next command, and a pin that is routinely wrong teaches people to ignore pins.

Everything else in the file was already in the manifest, and is repeated only so the lock stands alone on a machine that has never read one. version stays at 1 while uze is pre-release; the number exists to tell apart shapes a released uze wrote.

Staleness is decidable offline

Comparing the two files answers whether the lock still stands: no network, no re-resolution, so uze status works in a tunnel. A declaration is stale when the lock never resolved it, resolved it from a different marketplace, or the marketplace it comes from is declared differently now. A lock entry the manifest no longer declares is not staleness, it is a removal.

A floor, not a ceiling

A machine runs one revision of each plugin, shared by every project and every agent on it, and keeps it at the newest. The lock does not say what runs; it says what a machine starts from:

  • A machine without the plugin installs exactly the locked revision. This is the fresh clone, the teammate, the CI runner.
  • A machine behind the lock is raised to it by uze install. A teammate moved the pin and you pulled it.
  • A machine past the lock keeps what it has. uze install never downgrades and never moves a pin, and uze status shows the plugin as installed at a revision other than the lock's. That is the ordinary state after an update, not a fault; uze update in the project records it.

What these files are not

They are project scope. Neither records what is installed on your machine: that lives in the Store under ~/.uze, and uze install -m never writes here. The two are independent by design; see Machine and project.

On this page