v1.16.30

asheshgoplani/agent-deckv1.16.30Oct 18, 2026by github-actions[bot]

Upgrading?

AI Summary

Agent Deck v1.16.30 is a security-focused update that introduces cost budget enforcement, new triggers and watchers functionality, and enhances the macOS app and web terminal capabilities. The release includes significant security improvements around MCP config handling and terminal escape sequences.

Key Highlights

  • Security improvements: Refuses project MCP config symlinks that point outside the project and cleans terminal escapes from text
  • Cost budget enforcement: `[costs.budgets] enforce = true` stops agent sends once a limit is spent
  • New Triggers view: `agent-deck triggers` shows what wakes each session and whether it's healthy
  • Web terminal enhancements: Cmd-click opens links in sessions, file:// links open in browser panel
  • Watcher improvements: Live updates without restart, uses same profile as other commands

Breaking Changes

  • Project MCP configs that are symlinks to files outside the project are now refused instead of being written
  • Watchers created without `-p` while another profile was selected are now stored in `default` and show only with `-p default`
  • Scripts running `config set macapp.plugins true` without a terminal must add `--yes`
  • Local builds now auto-update to the published release of the same version
  • Goal files with `max_cycles: 0` now run with no cycle cap instead of failing

New Features

  • Run Claude slash commands safely with `agent-deck session command`
  • Opt-in browser for agents with `agent-deck browser`
  • `agent-deck session focus --remote <name> <id>` jumps TUI to remote session
  • New Triggers view with `agent-deck triggers` and `alt+t` in the TUI
  • Web terminal: Cmd-click opens links in sessions that capture the mouse
  • Cost budgets can be enforced
  • Security improvements for MCP configs and terminal escape sequences
  • New fan-out hint in Claude session identity blocks
  • Goals with opt-in progress controls
  • Watchers pick up changes live without restart
  • OpenCode 2.x sessions show running while sub-agents work
  • Restarting a Hermes entry resumes conversation more reliably
  • Conductor and inbox improvements with wake withdrawal
  • macOS app enhancements with plugin consent and browser panel
  • Web terminal file:// links open in browser panel from phone

Full Release Notes

## Agent Deck v1.16.30

Terminal session manager for AI coding agents.

### Installation

**Homebrew (recommended):**
```bash
brew install asheshgoplani/tap/agent-deck
```

**Quick Install:**
```bash
curl -fsSL https://raw.githubusercontent.com/asheshgoplani/agent-deck/main/install.sh | bash
```

**Go Install:**
```bash
go install github.com/asheshgoplani/agent-deck/cmd/agent-deck@v1.16.30
```

## What's new in 1.16.30

### Highlights

- Run Claude slash commands such as `/model` or `/compact` on a Claude session safely with `agent-deck session command`, or from a conductor chat with `/cmd <session> <command>` (see Sessions).
- Opt-in browser for agents: sessions can open and read pages in the Mac app's Browser panel with `agent-deck browser`. It is off by default; turn it on with `agent-deck config set browser.enabled true` and `agent-deck config set macapp.plugins true --yes` (see macOS app).
- `agent-deck session focus --remote <name> <id>` jumps the running TUI to a session on a remote (see Sessions).
- New Triggers view: `agent-deck triggers` and `alt+t` in the TUI show what wakes each session and whether it is healthy, with mute and run now (see Triggers).
- Web terminal: Cmd-click (Ctrl-click) opens links in sessions that capture the mouse, and `file://` links under `[web] file_link_roots` open the file through the deck, also from a phone (see Web and Fixes).
- OpenCode 2.x sessions show running while their sub-agents work (see Status).
- Model pickers stay current: OpenCode's list comes from the installed CLI, and `[models.<tool>]` pins or extends the list for Claude, Codex, Gemini and OpenCode (see Models).
- Watchers pick up changes live without a restart and use the same profile as every other command, and a remote's TUI link follows the profile the remote serves; thanks @AndreIntelas (see Watchers and Remotes).
- Cost budgets can now be enforced: `[costs.budgets] enforce = true` stops agent sends once a limit is spent (see Costs).
- Security: project MCP config links that leave the project are refused, text from gateways and session titles is cleaned of terminal escapes, and code scanning and Dependabot show no open alerts at release (see Security and toolchain).

### Security and toolchain

- agent-deck no longer writes your MCP definitions through a project's MCP config symlink that points outside the project. A cloned repository could ship `.mcp.json` (or `.cursor/mcp.json`, a `.cursor` directory link, or `opencode.json`) as a link to a file elsewhere, such as `~/.claude.json`, and the next session start or MCP change replaced that file with your MCP definitions, env values included; a dangling link created a file outside the project. Such a link is now refused, also when the outside file already holds the same content, and a dangling link (such as a `.cursor` directory link) is refused before anything is created. The error names the link and its target and says that `manage_mcp_json = false` in config.toml stops agent-deck writing project MCP config files. Links that stay inside the project still work. The plugin pin refresh no longer writes through a fixed `<file>.tmp` name, and the `.mcp.json` lock file is checked too. Config files in your home directory (`~/.claude.json`, `~/.cursor/mcp.json`) still follow links as before (#2737, #2742, #2749, #2757).
- `openclaw` commands drop control characters from text the gateway sends before printing it: rejection codes and messages, close reasons and other connect, list and send errors, and the channel and agent names shown by `openclaw status` and `openclaw list`. A hostile gateway can no longer send terminal escape sequences (such as a clipboard write) through them (#2735).
- Session titles drop control characters and Unicode bidi formatting characters wherever they are set or loaded: agent names stored by `openclaw sync`, CLI, web and TUI renames, Claude title sync, titles and groups from a remote's session list, and titles already stored by an earlier sync. Two titles that differ only in such characters now count as the same title. The tmux status line escapes `#` in the title and the project folder name, so a title or a cloned folder named like `#(cmd)` no longer runs that command on every status refresh. The bridge TUI cleans chat and history text, errors, agent names and the server version before showing them. JSON output is unchanged (#2741).
- A stored Claude session ID that contains a path separator, `..` or a glob character (`*`, `?`, `[`) no longer finds a transcript outside the Claude projects directory or another session's transcript; such an ID finds nothing. Plain IDs set by hand with `session set <id> claude-session-id` still work (#2730).

### Updates

- A local build (`<version>+local...`) is now offered the published release of the same version, so `update --check`, the auto-install and the releases-behind count move a machine running `1.16.29+local.<date>.<sha>` onto v1.16.29 or later instead of keeping the local build. Other build metadata (such as `+build.5`) still counts as the same version, and release candidate ordering is unchanged (#2710, #2711).
- `update`, `update --check`, `doctor` and the TUI's "restarted into" notice warn when an older `agent-deck` earlier on PATH (for example a `/usr/local/bin/agent-deck` link to an old build in front of `~/.local/bin`) keeps your shells, hooks and scripts on the old release. The warning names the copy, both versions and the fix: the `rm` command (with `sudo` when needed) or an `export PATH=...` line, or the package manager for a Homebrew, Nix, asdf, mise, snap or `/usr/bin` copy. agent-deck never removes the copy itself. `update --check --json` and `doctor --json` list such copies under `shadowed_by` (empty when PATH is clean). `update --unattended` does not check (#2725).

### Models

- OpenCode's model list comes from the installed CLI (`opencode models`, no prompt, about 3s timeout, cached per binary for up to a day and probed again after a CLI upgrade), merged in front of the built-in list, so new models show up in the TUI and web pickers and `launch -capabilities --json` without an Agent Deck release. A failure keeps the built-in list. `[models.<tool>] list = [...]` pins a tool's suggestions and `[models.<tool>] extra = [...]` adds IDs in front, for `claude`, `codex`, `gemini` and `opencode` (#2664, #2714, reported by @koenzhao).

### Sessions

- Restarting a Hermes entry resumes that entry's own conversation more reliably. The hook handler now records the conversation each entry's own turns report, so restart no longer depends on the TUI having sampled the right moment of a turn. `session start` and the web Start button resume the same conversation a restart would. A delegated subagent's conversation is never recorded, and neither is one from a nested `hermes` running outside the entry's directories. A fresh restart forgets the record, and a late hook from the old process can no longer bring it back. After `/new`, every later restart keeps the new conversation, and sandboxed entries resume the same way. Entries with nothing recorded behave as in 1.16.29 (#2582, #2722, #2745, #2760, reported by @renky).
- `agent-deck session focus --remote <name> <id> [--attach]` moves the running TUI to a session on a remote: the cursor lands on its row under Remotes, folded headers and filters that hide it open, and `--attach` opens it the way Enter does. Nothing local is created. An unknown remote exits 2 and changes nothing (#2704, #2715).
- `agent-deck session command <session> "/<command> [args]"` runs a Claude Code slash command (`/model`, `/compact`, `/clear`, `/exit`, or any other command or skill) in a Claude session and checks that it ran. Use it instead of `session send`, which presses Enter without looking at Claude's autocomplete picker and can run a different entry. It types nothing unless the composer is empty and the agent idle (`--wait-idle 60s` waits for that), never accepts a ghost suggestion or another picker entry, leaves a dialog it did not open alone, and reads the outcome from the pane: `/model` checks the model Claude reports, `/compact` the Compacted notice, `/exit` stops the session. `/model <id>` switches that session only: it picks the model in Claude's picker with "use this session only" and never changes the default model for new sessions, and it fails with `default_changed` if the saved default changes to the requested model anyway. It takes aliases (`haiku`, `sonnet`, `opus`, `default`, with or without `[1m]`) and full ids; a model that is not in the picker fails with `unknown_model` and lists the entries, and a bare `/model` is an argument error. A switched model is kept as the session's model, so restart and fork use it; `/model default` clears it. `/login` and `/logout` are refused, and only Claude sessions are supported for now. `--json` prints the result, with a `code` on failure (an unknown session exits 2); `--dry-run` types nothing; `--background` runs it detached, which a session needs when it targets itself. `agent-deck session commands [session]` lists the built-in, user, project and plugin commands and skills a session can run, without typing anything. Conductors get a `/cmd <session> <slash command>` quick command (and `/cmd <session> what can I run`) that uses it and replies with one line, so you can switch a session's model or compact it from Telegram or Slack; on those channels send it as `cmd ...` without the leading slash (#2734, #2736).
- Claude-family sessions (`claude` and custom tools that wrap it) that agent-deck launches get one more line in their identity block, telling them to fan out independent steps instead of doing them one by one. Other tools do not get it. `[launch] fanout_hint = false` (also `config set launch.fanout_hint false`) leaves it out, and `--no-identity` or `context_level = "none"` still write no block. It applies on the next start or restart (#2719).

### Remotes

- A remote set up without a profile (or with `default`) whose host selects another profile through `default_profile` or `AGENTDECK_PROFILE` pushes session changes to the TUI within a few seconds again, instead of only at the next 15-second fetch: `remote-agent` now watches the state DB of the profile it serves. If the host's `default_profile` changes while the TUI is connected, the link reconnects within about 5 seconds and follows the new profile. Remotes with an explicit profile are unchanged (#2755, #2756, thanks @AndreIntelas).

### Costs

- `[costs.budgets] enforce = true` (also `config set costs.budgets.enforce true`) turns the existing budget limits into a gate; without it they stay display-only. With it on, once a session's, its exact group's or the global daily, weekly or monthly limit is spent, agent sends and wakes are refused before anything is typed or queued: `session send` (and so goal nudges, conductor heartbeats and inbox wakes), `--queue` and queued sends, `launch -m` (nothing is created), `session start -m`, the "continue" after an MCP restart, and the TUI's heartbeat and watcher health alerts. A refusal exits 1 with `budget exceeded`, or `code: "BUDGET_EXCEEDED"` and `resets_at` under `--json`; at 80% there is a note on stderr. Messages people send through the chat bridges and the web Command Center are never blocked, and `--ignore-budget` on `session send`, `session start -m` and `launch -m` sends anyway. A group limit counts only sessions in exactly that group (#2727).
- Cost totals no longer count a Claude reply more than once. Claude writes one reply as several transcript records with the same usage, and the transcript import stored a cost row for each, while the Stop hook stored another copy of the last one. Both now record one row per reply. On first start, agent-deck removes the Stop hook copies stored next to an import row for the same turn (same session, model and token counts, within minutes); rows stored per record by earlier versions are folded the next time a transcript is imported, and `agent-deck costs sync` folds them for existing Claude sessions now. The TUI's clear-on-compact heartbeat, when a budget refuses it, is tried again once the budget allows (#2727, #2758).

### Goals

- The goal skill has opt-in progress controls; goals that do not set them run exactly as before. `no_progress_cap: N` waits twice as long before each wake of a worker that makes no progress, and after N such wakes runs `done_cmd` once more; if it still fails, the goal escalates with the worker left running (or stops the worker with `no_progress_action: "stop"`). `worktree_progress: true` counts changes in the worktree as progress, so a worker that edits files without writing receipts is not woken. `worktree_gate: true` skips rerunning `done_cmd` while the worktree is unchanged since it last failed. `goal.sh` takes them as `NO_PROGRESS_CAP`, `NO_PROGRESS_ACTION`, `NO_PROGRESS_BACKOFF_BASE_SECONDS`, `WORKTREE_PROGRESS` and `WORKTREE_GATE`, all off by default. See `skills/agent-deck/references/goal.md` (#2718).
- Goals take run budgets: `goal.sh --max-tokens`, `--max-turns` and `--max-minutes`, with defaults from `[costs.run_budget]` (`0` is no cap). Token counts leave out cache reads. When an enforced daily, weekly or monthly budget refuses a goal's nudge, the goal pauses until the budget resets, with the worker left running and only `done_cmd` checked meanwhile; a spent session budget ends the goal as failed and stops the worker. `max_cycles: 0` now means no cap (#2727).

### Conductors and inbox

- Conductors and launched children are told that a message needing nothing (a plain "ok", "thanks" or a status ping that changes nothing) gets no reply; a `[HEARTBEAT]` still does. Unedited conductor instruction files from v1.16.25 through v1.16.29 are updated in place, including the new `/cmd` row (see Sessions); edited ones are left alone (#2721, #2734, #2754).
- An inbox notice about a child's finished turn is withdrawn once the parent has read that turn's final reply with a plain `agent-deck session output <child>` after the turn ended: it no longer wakes the parent, blocks its Stop, or shows in a digest or the heartbeat count, and `inbox drain` says on stderr what it withdrew. Reads with `-q`, `--json`, `--copy`, `--pane` or a `--max-tokens` cut, reads during the turn, questions, errors and replies that were still in progress are never withdrawn. `inbox stats` counts `wakeups_withdrawn` and `records_withdrawn`. The conductor's own result reads now use plain `session output`, so they count (#2721, #2754).

### Status

- A finished Claude session with a leftover `run_in_background` shell (a dev server, a `tail -f`) no longer shows as running for hours. The shell counter in Claude's footer keeps a session running only while the transcript shows a recent background Bash launch, within `[status] background_shell_hold` (default `"3m"`); after that the session reads as waiting. A mixed counter with a monitor still reads as watching. `"0"` restores the old rule. A build started in the background that runs longer than the hold shows as waiting while it still runs, so raise the hold if that matters (#2733).
- An OpenCode 2.x session shows running while any of its sub-agents (and their sub-agents) is still working, and waiting once all of them are idle, as OpenCode 1.x sessions already did (#2511, #2716, reported by @3rwww1).

### Watchers

- Without `-p`, the `watcher` commands now use the same profile as every other command: `AGENTDECK_PROFILE`, then the profile inferred from `CLAUDE_CONFIG_DIR`, then `default_profile`. Before, they always used the `default` profile, so with any of those set a watcher was written to `default` and the selected profile's engine never ran it. Such a watcher now shows only with `-p default`. To move one: `agent-deck -p default watcher list` to find it, `agent-deck -p default watcher stop <name>`, move its `watcher.toml` aside, run the same `watcher create` again without `-p`, copy any hand edits back into the new `watcher.toml`, then `watcher start <name>`. The stopped row stays in `default`. `watcher help`, `install-skill`, `import` and `routes` touch no profile state and do not resolve the profile (#2740, #2746, #2753, thanks @AndreIntelas).
- A running watcher engine picks up changes within about 5 seconds without a restart: a watcher started, stopped or deleted from the CLI or TUI gains or loses its adapter (a stopped webhook frees its port), a changed `[source]` table in `watcher.toml` restarts only that watcher, and a changed `clients.json` swaps the routes. Other watchers are not touched. A file that does not parse leaves things as they were and is reported. `agent-deck watcher reload [--json] [--timeout 15s]` asks for a pass now, prints what changed and also retries watchers that failed to start; it exits 1 when something failed or the engine did not answer. The `[watcher]` table in config.toml is still read only at engine start (#2739, #2744, reported by @AndreIntelas).

### Triggers

- `agent-deck triggers` lists what wakes each session: launchd jobs and systemd user units that call agent-deck, heartbeats, chat bridges, watchers and other sessions, grouped by host, with what each one wakes, how often, its last fire, its health (`ok`, `failing`, `dead-target` when the target names no session, `never-fired`, or `unknown` for a remote that cannot answer) and how often it repeats itself. Filter with `--session`, `--host <remote>|all`, `--kind`, `--health problems` and `--all`; `--json` for scripts. `triggers mute <id> [--for 24h]` and `unmute` stop a trigger's sends reaching the pane without touching the host job (agent-deck never pauses launchd or systemd jobs), `triggers run <id>` starts a scanned unit once, `triggers fires <id>` shows recent fires and `triggers scan` rescans. In the TUI, `alt+t` opens the Triggers panel, or "What wakes <session>" on a session row, and rows show a `⚡N` badge, red when a trigger is failing. `session send --source <name>` (or `AGENTDECK_SOURCE`) tags a send; without it the sender is inferred from launchd, systemd, cron or ssh, and agent-deck's own heartbeats, chat bridges and command center tag themselves. Fires are kept for 7 days and store only a cleaned, redacted first line of the message (#2702, #2738).

### macOS app

- When `[macapp] plugins` is off, `limits` and `events publish` (and `events publish --help`) name the exact command that turns it on (`agent-deck config set macapp.plugins true --yes`), and `--json` errors carry a `hint` the app can show as a consent prompt. For `remote <name> limits` the hint says to run it on that remote, and its JSON carries `remote`, `remote_host` and `remote_argv` instead of `argv`. `config set macapp.plugins true` now asks `[y/N]` in a terminal; declining, or running without a terminal or with `--json` and no `--yes`, exits 2 (`CONSENT_REQUIRED` in JSON) and leaves config.toml unchanged. No consent is needed when plugins is already on, or to turn it off (#2732, #2748).
- The Mac app's own reply frames (`macapp.browser.response` and `macapp.app.focus`, besides `macapp.open` and `macapp.open.ack` as before) can be published without `[macapp] plugins`; `macapp.browser.request` still needs it. `events follow` and `events publish` take `--profile <name>` to use another existing profile's log; an unknown name or a path is refused. `remote <name> events publish` sends one of those reply kinds to a remote with the payload on stdin, capped at 2 MiB, and the remote accepts it only for one of its own sessions; a remote on an older release reports `unsupported remote command "events publish"`. `events publish --data-file` refuses payloads over 2 MiB. Every way a session is deleted (`remove`, `session remove`, `worktree finish`, `conductor teardown`, TUI and web delete) now publishes `macapp.session.closed`, and moving a session to another profile publishes it on the old profile with `reason: "moved"` (#2726).
- `agent-deck open` takes `--wait <duration>` to bound the wait for the app's answer (default 3s as before, at most 2m; `--wait 0` reports Queued at once) and `--browser-profile <name>` to pick a Browser panel profile in the app (a plain name of up to 64 characters). Each request carries a random `request_id` the app can use to drop a repeat (#2728).
- Agents can drive their own tabs in the Mac app's Browser panel with `agent-deck browser open|tabs|close|click|type|read|shot`, for local sessions and sessions on a remote, through the session's events log (no new socket). It is off by default and needs two settings: `agent-deck config set browser.enabled true` (or `agent-deck config set profiles.<name>.browser.enabled true` for one profile, which overrides the global value either way) and `[macapp] plugins = true` (`agent-deck config set macapp.plugins true --yes`). While `browser.enabled` is off, `browser` and any `events publish --kind macapp.browser.request` exit 2 (`BROWSER_DISABLED` with `--json`) and name the setting. Other exit codes: 0 ok, 1 error, 3 app not connected, 4 the owner is driving the browser. Page text, titles and URLs are cleaned of control and bidi characters before printing, and large results (page text, screenshots) pass through a private file instead of the log. On a host the app reaches as a remote, `browser open` and `open` refuse loopback, private, link-local, tailnet and other internal addresses before sending anything; the Mac app enforces its own address policy. Neither setting protects against an agent on the same host, which controls its own config; see `docs/macapp-browser.md`. `open` and `browser` find the calling session from its tmux session when `--session` and `$AGENTDECK_INSTANCE_ID` are unset (#2729).

### Web

- With `[web] file_link_roots = ["~/projects", ...]`, a `file://` link in the web terminal under one of those directories opens the file in a new tab through the deck, with the usual token login, so it works from a phone too. Images are shown, other text (HTML and SVG included) is shown as plain text, and anything else downloads; only regular files up to 32 MiB inside a root can be read, symlinks that lead out are refused, and `/` or your home directory is not accepted as a root. Without roots (the default), outside them, or for a link naming another host, the click still copies the path. `agent-deck file read <path>` and `agent-deck file roots` (also through `remote <name>`) apply the same rules, and the Settings drawer lists the roots (#2676, #2717, reported by @bautrey).

### Fixes

- Web: Cmd-click (Ctrl-click on Linux and Windows) opens a terminal link in a session that captures the mouse, such as tmux with `mouse on` or a fullscreen agent, where a plain click went to the app. The link opens on release through the usual confirm and trusted domains check, and dragging off cancels. A `file://` link no longer opens a blank tab: it copies the path and shows a toast, unless it is under `[web] file_link_roots` (see Web). Other non-web links (`javascript:`, `data:`, `vscode:`, `mailto:`) open nothing and show a toast. Plain clicks, Option-drag selection and touch scrolling are unchanged (#2675, #2676, #2713, reported by @bautrey).
- A sub-session row no longer shows a `▾N` fold badge or takes `Tab`, `l`, `→` and `h` for a fold that hid nothing, so those keys work on it again. Parent rows are unchanged (#2723).
- The TUI no longer crashes on a very narrow preview pane (9 to 12 columns, such as a remote group row). Dialog hints, paths, cost dashboard titles, MCP names, badges and remote titles are cut by display width, so non-ASCII text is no longer split mid-character. A TUI that crashes or is killed by a signal now turns mouse reporting and the other terminal modes off, so the shell is usable afterwards (#2720).
- On macOS an idle TUI no longer uses most of a CPU core rereading every file in the shared hooks directory four times a second; it skips directories that have not changed. Linux is unaffected (#2731).
- Text sent to a session that ends in `;` keeps the `;`; tmux took it as a command separator and dropped it (#2734).

### Upgrading

- On a host the Mac app reaches as a remote, `agent-deck open` now refuses loopback, private, link-local, tailnet and other internal addresses.
- Cost totals for Claude sessions can go down after the upgrade, as duplicate rows are removed; run `agent-deck costs sync` to fold older rows at once.
- Project MCP configs: a project `.mcp.json`, `.cursor/mcp.json` or `opencode.json` that is a symlink to a file outside the project (for example a monorepo `pkg/.mcp.json -> ../.mcp.json`, or a dotfiles link) is now refused instead of written. `add --mcp` and `launch` fail with the error, the TUI and web MCP dialogs show it, and a restart starts the session without the MCP change. Replace the link with a real file or a link inside the project, or set `manage_mcp_json = false`.
- Scripts that run `config set macapp.plugins true` without a terminal must add `--yes`. A declined prompt now exits 2.
- Local builds: a machine on a `+local` build of a version now auto-updates to the published release of that same version when auto-install is on.
- Watchers created without `-p` while `AGENTDECK_PROFILE`, `CLAUDE_CONFIG_DIR` or `default_profile` selected another profile were stored in `default` and now show only with `-p default`; see Watchers for how to move one.
- Claude-family sessions get the fan-out line in their identity block on their next start or restart; set `[launch] fanout_hint = false` to keep the old block.
- Goal files with `max_cycles: 0` now run with no cycle cap instead of failing at once.
- `events publish --data-file` refuses payloads over 2 MiB.

Thanks to @AndreIntelas for the watcher and remote-agent profile fixes, to @Totopo27 (Luis Araya), whose #2759 was a correct fix for the same remote-agent profile issue (#2755) written in parallel and closed only because the reporter's own PR (#2756) landed first, to @kickinrad for replacing a realistic-looking example token in the sanitizer docs (#2767), and to @koenzhao, @bautrey, @renky, @3rwww1 and @AndreIntelas for their reports.

---

**Full Changelog**: https://github.com/asheshgoplani/agent-deck/compare/v1.16.29...v1.16.30


Related Repositories