Extensions
What the workspace shows beside your agents, the official ones, how you allow them, and how to write your own
An extension is something the workspace shows beside your agents: a line in the bar under the agent area, a drawer beside the agent, a block of the sidebar, a band over its pane, a full-frame view. Harnesses never see one. Four are built in (Spec, Architect, Code and Review), and a plugin can bring more.
Official extensions
The official marketplace carries three, in one plugin, workspace. One
install brings all three, and each stays off until you allow what it may
reach and enable it:
uze add workspace@uzeThis declares the plugin in the project's agents.yaml, so anyone who runs
uze install there gets it too; uze add -m workspace@uze keeps it on
this machine only, and uze remove workspace takes it out. To try them
without a project of your own, ask any agent for /uze:try.

gsd
READMEA GSD milestone beside the agent: the step it is at, the next one a click away, and a checkpoint answered over its pane.
- Shows in
- bardrawerband
- May
- Read the checkout, type into an agent once you confirm, and notify you. Enabled one project at a time.

focus
READMEA pomodoro: the time left in the bar, a sound when a session ends, and the sessions of the last seven days.
- Shows in
- bardrawer
- May
- Keep its sessions on this machine and notify you.

contributions
READMEThe repository's commits as a contribution graph, day by day, with the commits of the day you click.
- Shows in
- bardrawer
- May
- Run git, confined where the machine confines programs, and remember the day you chose.
The Extensions screen
Under management (Ctrl+O), Extensions lists every extension on this
machine. The built-in ones wear Bundled, and those from uze's own
marketplace wear Official. Select one to open its drawer: the plugin it
comes from, whether it runs here, what it may reach, and its preferences,
which you set right there.
An extension a plugin brings runs nothing until you allow it. Press Enter
on it to enable it: a dialog names it and lists what it may reach, such as
reading the checkout, running git or typing into an agent, and Allow and
enable turns it on. Until then, where it would show, it says it needs
consent.
- Enabling one here turns it on in every project. One that reaches your agents is the exception: it is enabled one project at a time.
- An update that wants more than you allowed is held and runs nothing until you allow it again. One that wants the same or less is applied as it comes.
- A program an extension runs, such as
git, is asked about on a step of its own, which says where it was found and whether it runs confined. - Only you can allow one. An agent cannot answer for you from its pane.
When you open a project whose plugins bring an extension that is not enabled there, the workspace offers it in one dialog, for that project or for every project. Putting the dialog away answers nothing: it is offered again the next time the project opens.
Turning one off
Under management (Ctrl+O), on Extensions, select one and press Enter
to switch it off or back on. The built-in ones start on. Turning one off
takes everything it adds with it at once: its button, its keys (they go back
to the program in the pane), its entry in the action index (F1) and its
sidebar section.
The choice for a built-in one is kept in ~/.uze/config.toml. From a
terminal, uze config extension architect off does the same, and
uze config extension lists them. You can also write it by hand:
[extensions]
architect = falseAn extension's own preferences are edited in the same place and kept beside that choice, under its id. They stay there when its plugin is removed, and a value the extension does not admit reads as its default.
[preferences.review]
mark-viewed-automatically = falseWriting an extension
An extension never draws. It declares what it shows and where its data comes from, and the workspace draws it, in the operator's theme and glyphs, with the same keys and mouse as everything else there. Its logic runs in a bounded script engine that reaches only what the operator allowed.
The uze:author skill writes extensions too:
/uze:author add an extension to my plugin that lists the open items of TODO.mdIt cannot allow the extension for you: it tells you what the extension asks for and where to allow it.
Its files
Each extension is a directory under extensions/, named by its id, and a
plugin may carry several.
| File | What it holds |
|---|---|
extension.kdl | The manifest, in KDL: the surfaces, where their data comes from, the actions, and the grants it asks for |
*.uze | Components: a <view> block in KDL, built from the catalog's nodes, a <script> block in Rhai, or both |
fixtures/*.json | A sample of what a source answers, for tests and for a view with no script |
tests/*.test.kdl | Tests the check runs, and their snapshots |
To scaffold one into a plugin you are writing:
uze agent plugin create my-plugin --market my-market --extension todoIt writes a manifest with a full-frame view and a sidebar section, the
component they draw, a fixture, and tests that pass. The first time it writes
an extension into a marketplace, it also adds this line to the marketplace's
.gitattributes, creating the file if there is none, so forges highlight
components as KDL:
*.uze linguist-language=KDLWhere it shows
A surface names its slot:
| Slot | Where |
|---|---|
bar | One line in the bar under the agent area, on the left or the right. A click opens the drawer or view it names |
drawer | A panel on a side of the agent area, beside the pane or floating over it |
view | The full frame, opened from its door in the bar |
section | A block of the sidebar |
status | One row of the sidebar's header |
band | One to six rows along the top or bottom of each agent's pane |
overlay | A card over the frame, opened by an action and closed by Esc |
A band's rows are taken before the agent's pane is sized, and a floating drawer keeps the agent's terminal at its size, so neither makes the agent rewrap what it printed.
A worked example
This extension counts the open items of the checkout's TODO.md in the bar,
and lists them in a drawer a click on the count opens.
protocol 1
id "todo"
name "Todo"
description "The open items of the checkout's TODO.md"
wants "read:checkout" "clipboard"
surface "count" slot="bar" view="count.uze" source="items" opens="list"
surface "list" slot="drawer" view="list.uze" source="items" title="Todo"
source "items" {
script "items.uze"
input file="TODO.md"
applies-when "TODO.md"
refresh-on "paths.changed" "TODO.md"
}
action "copy" label="Copy the item" on="list" key="ctrl-y" {
clipboard "{text}"
}The source runs the script in items.uze with its input, only in a
checkout that has a TODO.md, and again whenever that file changes. The
action copies the selected item. wants lists every grant the extension asks
for: the check refuses an action or a script that needs one it does not list.
Each view binds the source's data with {field}:
<view>
Row gap=1 {
Icon "check" role="info"
Text "{count} open"
}
</view><view>
Stack gap=1 {
Text "{count} open in TODO.md" role="muted"
List each="items" empty="Nothing open" {
Text "{text}"
}
}
</view>A colour is always a role (muted, info, warning), never a value, and an
icon is named, never typed as a glyph: the theme and the glyph set the
operator chose decide how each one looks.
The script answers with an envelope whose data is what the views bind:
<script>
// Every line of the file that opens with `- [ ] `.
fn answer(input, context) {
let open = [];
for line in lines(read_checkout(input.file)) {
if line.starts_with("- [ ] ") {
open.push(#{ text: line.sub_string(6) });
}
}
#{ data: #{ items: open, count: open.len() } }
}
</script>read_checkout exists only because the manifest wants read:checkout. A
script has no import or eval, and is stopped past its bounds: a million
operations, 100 ms, 1 MiB strings.
A test opens a surface with a fixture in place of the script, and acts on it as the operator would:
{
"count": 2,
"items": [
{ "text": "Write the view" },
{ "text": "Check the plugin" }
]
}test "the bar counts the open items" {
surface "count"
given "items" fixture="fixtures/items.json"
expect-text "2 open"
}
test "the drawer lists them, and ctrl+y copies one" {
surface "list"
given "items" fixture="fixtures/items.json"
expect-text "Check the plugin"
press "ctrl+y"
expect-action "copy"
snapshot "list"
}Sources and actions
A source says where a surface's data comes from:
| Source | Answered by |
|---|---|
fixture | A JSON file of the extension. Runs nothing, so it asks for no consent |
script | The answer function of a component's <script> |
run | A program, given as an argv and never through a shell: a file of the extension, or a program an exec: grant names |
agents | The workspace: every agent of the open spaces and its state |
events | The workspace: the last events the extension saw |
resident | A script kept running while its surface is shown, with its own clock and keys, for a game or a live view |
A source asks again on a clock (every "30s", while a surface showing it is
in view), when files beneath a path of the checkout change, when the
checkout's branch, head or working tree changes, or when an agent starts or
finishes a turn or a tool. An action is what a person can do on a surface: call a script
function, ask a source again, copy, open a file of the checkout, open another
surface, or type a prompt into an agent, which the workspace shows first and
sends only to an agent that is ready. Inputs (Field, Select, Form) and
preferences the operator sets on the Extensions screen feed both.
Grants and consent
Installing a plugin does not let its extensions run: the operator allows
each one, grant by grant, on the Extensions screen.
Each grant in wants is one line of that question:
| Grant | Lets the extension |
|---|---|
read:checkout | Read files beneath the checkout's root |
clipboard, open-path | Copy text, open a file of the checkout |
exec:<program> | Run that program |
store, store:global | Keep a small JSON record per project, or one for the machine |
notify | Send a notification and a sound, rate-limited |
agent:observe | See the workspace's agents and what they do |
agent:prompt | Type a prompt into an agent, shown first |
net:<host> | Fetch from that one host, over HTTPS |
mcp:<server>/<tool> | Call one tool of an MCP server a plugin delivers |
resident | Keep its script running while it is shown |
No grant writes the checkout. Ask for the fewest that do the job.
- A view fed only by fixtures needs no consent. Anything that runs waits until the operator allows it, and the surface says so meanwhile.
- An update that asks for a new grant is held until it is allowed again.
- A program is pinned to the path found when it was allowed and, where the
machine can, confined: it reads the checkout and the system, writes only
its own scratch folder and opens no socket. Linux, macOS and Windows each
confine programs;
uze doctorsays how this machine does. How a program is confined →
Check, test and iterate
uze agent plugin check my-plugin # compile, bind and test every extension
uze agent plugin check my-plugin --update-snapshots # write the snapshots a change you meant moved
uze agent plugin check my-plugin --run # run the scripts against this checkout too
uze update my-plugin -m # deliver the change to the workspaceThe check compiles every extension with the rules the workspace hosts it
with, refuses a script that calls a host function its grants do not give it,
binds each view to its fixture, and runs every tests/*.test.kdl. Each
finding names its file, line and column, and how to fix it.
A test runs nothing the plugin ships, so it passes the same on any machine.
It opens a surface, feeds it a fixture, then clicks, presses keys and expects
text or an action. snapshot keeps the cells it drew, as text and colour
roles rather than colours, so a theme change never breaks it. A snapshot
missing or changed fails the check until --update-snapshots writes it, and
the diff is yours to review.
Editor support
uze agent plugin lsp is a language server for extension.kdl, components
and tests: completion and hover from the catalog, and the check's own
diagnostics as you type. Inside a <script> block it answers nothing yet.
Editors know no .uze language, so name one and point it at the server:
[language-server.uze]
command = "uze"
args = ["agent", "plugin", "lsp"]
[[language]]
name = "uze"
scope = "source.uze"
file-types = ["uze"]
language-servers = ["uze"]
[[language]]
name = "kdl"
language-servers = ["uze"]The full reference
Every slot, source, grant, event, action and catalog node, with each node's
properties, is in the
extension reference,
generated from the contract the workspace checks against. Its
machine-readable form is
schema.json.
The uze:author skill's
extension guide
covers programs, resident scripts, the agents and the store in depth, and
plugins/workspace
holds the official extensions, with their tests, to read as examples.