Skip to content
Back to skills

Workspace

ASecurity

bonsai.yaml, this project's Bonsai settings: what each field means, its allowed values and the template bonsai init writes. Use it to read or change a setting: a pack's ref or its roles' limits, where documents live, the protected paths, the ladder, how long generated files are kept.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 10, 2026
developmentgoshellbashgit

Works with

  • claude code
  • cli

Security analysis

A100/100

Scanned November 10, 2026

npx -y skills add LastStep/Bonsai --skill workspace --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Workspace?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Workspace
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/laststep-workspace/badge)](https://www.skillsdirectory.com/skills/laststep-workspace)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: workspace
description: "bonsai.yaml, this project's Bonsai settings: what each field means, its allowed values and the template bonsai init writes. Use it to read or change a setting: a pack's ref or its roles' limits, where documents live, the protected paths, the ladder, how long generated files are kept."
---

<!-- Generated by go generate from Bonsai's code (internal/reference/gen); never edit it by hand: a test rebuilds it and fails on any difference. -->

# bonsai.yaml: this project's Bonsai settings

**What it is.** `bonsai.yaml`, at the project's top folder, holds the project's Bonsai settings in the format `bonsai.workspace/1`: the packs it links and their refs, where it keeps the documents Bonsai knows, the paths agents change only under a grant, the ladder that proves work done, how long generated files are kept, and the limits of its packs' roles. It is the one Bonsai file a person edits. `bonsai init` writes it from the template below, a comment on every key; a writer keeps the comments, and `bonsai check` does not require them.

**When to use this skill.** Before reading or changing a setting: what a field means, the values it takes, and what changing it does. Every field and every allowed value is also printed by `bonsai check --schema bonsai.workspace`.

**How a change takes effect.** The file is committed and person-only: an agent changes it only while its running task's `bonsai.allows` holds it, which a person grants. A pack's ref, a pack added or taken out, `never_edit`, `never_read` and the folders under `documents` take effect at `bonsai update` (it previews every change; `--yes` writes them). The ladder, its floor, the ratchets and `ci_marked_tests` count as committed at the main checkout's HEAD, and one that sets a check looser (a rung or a floor entry taken out or changed, a rung added, a ratchet lowered) waits for a person's `bonsai update` to record it. A role's limits under `packs[].roles` and the `messages` thresholds change any time: raising a default or a max is never a loosening.

**What `never_read` stops, from Claude Code's permissions docs.** Each path becomes the deny rule `Read(/<path>)`, anchored at the project's top folder. It also denies the Edit and Write tools on the path (not NotebookEdit: list the path in `never_edit` as well for that). Claude Code applies it best effort to Grep, Glob, `@` mentions and the shell reads it recognises (`cat`, `head`, `tail`, `sed` and others); it does not stop a command that reads without naming the file (`grep -r` from its folder) or a script that opens the file itself. For shell reads, Claude Code's sandbox (`sandbox.filesystem.denyRead` in its settings) blocks every process: it is off by default, and native Windows has none. Bonsai's guard does not judge reads.

## Fields

| Field | Meaning | Allowed values | Example |
|---|---|---|---|
| `format` | The format and its version; a newer one is refused, never guessed | `bonsai.workspace/1` | `"bonsai.workspace/1"` |
| `id` | This project's id, written once by bonsai init; a copy meant as a new project: bonsai init --new-id | text matching `^ws-[a-z0-9]{26}$` | `"ws-7kq2m4xw5r3t6y2u7p4a5c3e2b"` |
| `name` | A short name: a lower-case letter, then up to 39 lower-case letters, digits and dashes | text matching `^[a-z][a-z0-9-]{0,39}$` | `"example"` |
| `packs` | The packs this project uses, applied in this order (a path two packs write stops update) | a list of mappings (their fields below) | as in the template |
| `packs[].id` | The pack's id, as its bonsai/pack.yaml says | text matching `^[a-z][a-z0-9-]*$` | `"base"` |
| `packs[].source` | The git repository the pack comes from, fetched with this machine's git | text matching `^[^-]` | `"https://example.com/packs/workflow.git"` |
| `packs[].path` | The pack's folder inside that repository; null for the repository's top | text matching `^[^/\\:][^\\:]*$`, or null | `"packs/base"` |
| `packs[].ref` | A release tag (base-v1.0.0 for base) or a 40-character commit; change it, then run bonsai update | text matching `^[^-]` | `"base-v1.0.0"` |
| `packs[].roles` | This project's limits for the pack's roles; null keeps the pack's; changed any time | a mapping, or null | under Mappings, below |
| `documents` | Where this project keeps the documents Bonsai knows: a folder (one file each) or one file | a mapping (its fields below) | as in the template |
| `documents.task` | The tasks' folder: one file per task, named <id>-<slug>.md (bonsai.task/1) | text matching `^[^/\\:][^\\:]*$` | `"work/tasks"` |
| `documents.run` | The run reports' folder: one report per agent session (bonsai.run/1) | text matching `^[^/\\:][^\\:]*$` | `"work/runs"` |
| `documents.answers` | The file for answers to questions that name no file of their own | text matching `^[^/\\:][^\\:]*$` | `"work/answers.md"` |
| `documents.memory` | The memory notes' folder, with their INDEX.md, which the block in CLAUDE.md imports | text matching `^[^/\\:][^\\:]*$` | `"work/memory"` |
| `documents.protocols` | The folder the packs' always-on protocol files go to; the block in CLAUDE.md imports each | text matching `^[^/\\:][^\\:]*$` | `"work/protocols"` |
| `protected` | Paths (globs) an agent changes only while its running task lists them in bonsai.allows | a list of text matching `^[^/\\:][^\\:]*$` | as in the template |
| `person_only` | Of those, the paths only a person grants (an approve or grant tap) | a list of text matching `^[^/\\:][^\\:]*$` | `[".claude/**","bonsai.yaml",".bonsai/lock.json"]` |
| `never_edit` | Paths no agent changes: deny rules Edit(/<path>), anchored at the project's top folder; e.g. ["ledger.json"] | a list of text matching `^[^/\\:~()][^\\:()]*$` | `["work/ledger.json"]` |
| `ladder_floor` | The rungs every task climbs, whatever its bonsai.ladder lists; e.g. [0, 1] | a list of a whole number from 0 | `[0,1,2,3,4]` |
| `ladder` | The rungs: what each runs and proves (rung, name, kind, command, required, timeout_s and more) | a list of mappings (their fields below) | as in the template |
| `ladder[].rung` | The rung's number; ladder_floor and a task's bonsai.ladder name rungs by it | a whole number from 0 | `0` |
| `ladder[].name` | Its short name, or null | text, or null | `"unit tests"` |
| `ladder[].kind` | Guard (rung 0: the diff against the task's grants), command, or ver-git | `guard`, `command`, `ver-git` | `"command"` |
| `ladder[].command` | The shell command a command rung runs (never bash by name); null for the other kinds | text, or null | `"go test ./..."` |
| `ladder[].required` | True: a red result here skips the rungs after it | true or false | `true` |
| `ladder[].timeout_s` | The longest it may run, in seconds; null for the runner's default | a whole number from 1, or null | `600` |
| `ladder[].ratchet` | The ratchet count it feeds (see ratchets); null for none | any value | `"tests"` |
| `ladder[].capture` | The patterns that pick numbers or text out of its output, by name; null for none | any value | under Mappings, below |
| `ladder[].means` | One line: what it proves when green; null for none | text, or null | `"Every unit test passes."` |
| `ladder[].tests` | The form of its test output: TAP, go test -json or JUnit; null when it prints no tests | text, or null | `null` |
| `ladder[].base_setup` | The command run first in a worktree at the merge base (installing dependencies); null for none | text, or null | `"npm ci"` |
| `ratchets` | Counts that may only rise, by name, each the floor a rung's count may not fall below; e.g. tests: 120 | a mapping | under Mappings, below |
| `ci_marked_tests` | Tests allowed to skip in CI only, each by its name in the test output; e.g. ["TestNeedsADisplay"] | a list of text | `["TestNeedsADisplay"]` |
| `generated` | How long generated files are kept, per kind; null keeps all (skill base:generated-files) | a mapping (its fields below) | as in the template |
| `generated.log` | The log, one file per session, in .bonsai/local/log/; default: 30 days after a file's last line | a mapping (its fields below) | under Mappings, below |
| `generated.<kind>.keep_days` | Clean what is older than this many days; null: no age rule | a whole number from 0, or null | `30` |
| `generated.<kind>.keep_newest` | Keep only this many of the newest; null: no count rule | a whole number from 0, or null | `100` |
| `generated.asks` | Questions for a person and their answers, in .bonsai/local/asks/; default: kept | a mapping (its fields below) | under Mappings, below |
| `generated.ladder` | Ladder results by task and commit, in .bonsai/local/ladder/; default: 7 days after the result's finished | a mapping (its fields below) | under Mappings, below |
| `generated.run` | Run reports, committed history, in the run reports' folder (documents.run); default: kept | a mapping (its fields below) | under Mappings, below |
| `generated.sessions` | Rows of the sessions table, in .bonsai/sessions.md; default: kept | a mapping (its fields below) | under Mappings, below |
| `generated.messages` | Messages between agents, in .bonsai/local/messages/; default: 30 days after a thread's last message | a mapping (its fields below) | under Mappings, below |
| `generated.agents` | Each agent's state, in .bonsai/local/agents/; default: 30 days after a session folder's last line | a mapping (its fields below) | under Mappings, below |
| `messages` | When bonsai messages flags a thread; null for Bonsai's defaults (bonsai check --schema bonsai.messages) | a mapping (its fields below), or null | `null` |
| `messages.unanswered_minutes` | Minutes a brief may go with no report once its agent is quiet; null for the default | a whole number from 0, or null | `120` |
| `messages.circling_briefs` | Briefs each way one thread may hold before it is flagged circling; null for the default | a whole number from 0, or null | `3` |
| `never_read` | Paths no agent reads: deny rules Read(/<path>), best effort for shell reads; e.g. [".env"] | a list of text matching `^[^/\\:~()][^\\:()]*$` | `[".env",".env.*","secrets/**"]` |

## The template

What `bonsai init` writes, every field present and a comment on every key; its id, name and pack are the project's own (here placeholders). Its ladder is empty: a project's rungs are its own, since only it knows its test command.

```yaml
# bonsai.yaml: this project's Bonsai settings (format bonsai.workspace/1), the one Bonsai file a person edits.
# bonsai init wrote it from Bonsai's built-in template, a comment on every key. It is committed and person-only:
# an agent changes it only while its running task's bonsai.allows holds it, which a person grants.
# To change a setting, edit its value, then run: bonsai update (it previews every change; --yes writes them).
# Every field, its allowed values and an example: bonsai check --schema bonsai.workspace
format: bonsai.workspace/1            # the format and its version; a newer one is refused, never guessed
# this project's id, written once by bonsai init; a copy meant as a new project: bonsai init --new-id
id: ws-abcdefghijklmnopqrstuvwxyz
# a short name: a lower-case letter, then up to 39 lower-case letters, digits and dashes
name: example
# the packs this project uses, applied in this order (a path two packs write stops update)
packs:
  - id: example-pack                  # the pack's id, as its bonsai/pack.yaml says
    # the git repository the pack comes from, fetched with this machine's git
    source: "https://example.com/packs/example-pack.git"
    path: null                        # the pack's folder inside that repository; null for the repository's top
    # a release tag (base-v1.0.0 for base) or a 40-character commit; change it, then run bonsai update
    ref: "v1.0.0"
    # this project's limits for the pack's roles; null keeps the pack's; changed any time
    roles: null
# where this project keeps the documents Bonsai knows: a folder (one file each) or one file
documents:
  task: work/tasks                    # the tasks' folder: one file per task, named <id>-<slug>.md (bonsai.task/1)
  run: work/runs                      # the run reports' folder: one report per agent session (bonsai.run/1)
  answers: work/answers.md            # the file for answers to questions that name no file of their own
  # the memory notes' folder, with their INDEX.md, which the block in CLAUDE.md imports
  memory: work/memory
  # the folder the packs' always-on protocol files go to; the block in CLAUDE.md imports each
  protocols: work/protocols
# paths (globs) an agent changes only while its running task lists them in bonsai.allows
protected: [".claude/**", "bonsai.yaml", ".bonsai/lock.json"]
# of those, the paths only a person grants (an approve or grant tap)
person_only: [".claude/**", "bonsai.yaml", ".bonsai/lock.json"]
# paths no agent changes: deny rules Edit(/<path>), anchored at the project's top folder; e.g. ["ledger.json"]
never_edit: []
ladder_floor: []                      # the rungs every task climbs, whatever its bonsai.ladder lists; e.g. [0, 1]
# the rungs: what each runs and proves (rung, name, kind, command, required, timeout_s and more)
ladder: []
# counts that may only rise, by name, each the floor a rung's count may not fall below; e.g. tests: 120
ratchets: {}
# tests allowed to skip in CI only, each by its name in the test output; e.g. ["TestNeedsADisplay"]
ci_marked_tests: []
# how long generated files are kept, per kind; null keeps all (skill base:generated-files)
generated:
  # the log, one file per session, in .bonsai/local/log/; default: 30 days after a file's last line
  log:
    keep_days: 30                     # clean what is older than this many days; null: no age rule
    keep_newest: null                 # keep only this many of the newest; null: no count rule
  asks:                               # questions for a person and their answers, in .bonsai/local/asks/; default: kept
    keep_days: null                   # clean what is older than this many days; null: no age rule
    keep_newest: null                 # keep only this many of the newest; null: no count rule
  # ladder results by task and commit, in .bonsai/local/ladder/; default: 7 days after the result's finished
  ladder:
    keep_days: 7                      # clean what is older than this many days; null: no age rule
    keep_newest: null                 # keep only this many of the newest; null: no count rule
  # run reports, committed history, in the run reports' folder (documents.run); default: kept
  run:
    keep_days: null                   # clean what is older than this many days; null: no age rule
    keep_newest: null                 # keep only this many of the newest; null: no count rule
  sessions:                           # rows of the sessions table, in .bonsai/sessions.md; default: kept
    keep_days: null                   # clean what is older than this many days; null: no age rule
    keep_newest: null                 # keep only this many of the newest; null: no count rule
  # messages between agents, in .bonsai/local/messages/; default: 30 days after a thread's last message
  messages:
    keep_days: 30                     # clean what is older than this many days; null: no age rule
    keep_newest: null                 # keep only this many of the newest; null: no count rule
  # each agent's state, in .bonsai/local/agents/; default: 30 days after a session folder's last line
  agents:
    keep_days: 30                     # clean what is older than this many days; null: no age rule
    keep_newest: null                 # keep only this many of the newest; null: no count rule
# when bonsai messages flags a thread; null for Bonsai's defaults (bonsai check --schema bonsai.messages)
messages: null
# paths no agent reads: deny rules Read(/<path>), best effort for shell reads; e.g. [".env"]
never_read: []
```

## An example ladder

Two rungs a project might add: the guard rung and its own tests, both on the floor. A rung added is a person's step: the next climb refuses it until a person's `bonsai update` in the main checkout records it.

```yaml
ladder_floor: [0, 1]                  # every task climbs both rungs
ladder:                               # the rungs, cheapest first
  - rung: 0                           # rung 0: the guard rung
    name: "guard"                     # its short name
    kind: guard                       # the branch's changes against the running task's grants
    command: null                     # only a command rung runs a command
    required: true                    # red here skips the rungs after it
    timeout_s: 120                    # two minutes
    ratchet: null                     # no count to keep from falling
    capture: null                     # nothing picked out of its output
    means: "Every change is inside the task's grants."   # what green proves
    tests: null                       # it prints no tests
    base_setup: null                  # nothing to set up on the base
  - rung: 1                           # the project's tests
    name: "tests"                     # its short name
    kind: command                     # a shell command, the same in sh and cmd
    command: "go test -json ./..."    # the project's own test command
    required: true                    # red here skips the rungs after it
    timeout_s: 600                    # ten minutes
    ratchet: tests                    # its count, the tests that passed, may not fall below ratchets.tests
    capture: null                     # the count is read from the tests themselves
    means: "Every test passes, and no fewer than the floor."   # what green proves
    tests: go-test-json               # its output's form: each test named
    base_setup: null                  # nothing to set up on the base
ratchets:                             # counts that may only rise
  tests: 0                            # raised to a green climb's count once there is one
```

## Mappings

The fields whose example is a mapping, written as `bonsai.yaml` holds them: indented `key: value` lines, nested by the field's path. Never write a mapping with content on one line in braces (a flow mapping): Bonsai refuses the file, and in `bonsai.yaml` that stops every call until a person repairs it; `{}` alone is an empty mapping.

```yaml
packs:
  - roles:
      builder:
        minutes:
          default: 240
          max: 480
ladder:
  - capture:
      tests: "([0-9]+) passed"
ratchets:
  tests: 120
generated:
  log:
    keep_days: 30
    keep_newest: null
  asks:
    keep_days: null
    keep_newest: null
  ladder:
    keep_days: 7
    keep_newest: null
  run:
    keep_days: null
    keep_newest: null
  sessions:
    keep_days: null
    keep_newest: null
  messages:
    keep_days: 30
    keep_newest: null
  agents:
    keep_days: 30
    keep_newest: null
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in development →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.