Presets & Shortcuts
Applies to: ChengOS v0.1.0+ | Last verified: 2026-08-12 | Source:
deploy/config/presets.yaml,deploy/config/shortcuts.yaml,crates/cheng-core/src/domain/preset.rs,crates/cheng-core/src/domain/shortcut.rs,crates/cheng-api/src/services/presets.rs,crates/cheng-api/src/services/shortcuts.rs
Two YAML catalogs let you change a workflow's behaviour without editing the workflow:
| Presets | Shortcuts | |
|---|---|---|
| Changes | A node's configuration | The routing of the next turn |
| File | presets.yaml | shortcuts.yaml |
| Scope | Until changed again | One turn, or until cleared |
| Consumer | The target node | ai/llm_branch |
Both are merged from a global file and a per-workspace file, and both are cached with the file's mtime as the invalidation signal — edit the YAML and the change is picked up without a restart.
<global_dir>/presets.yaml ← shared by all workspaces
<workspace-root>/<workspace-id>/config/presets.yaml ← per workspace, wins on id conflict
Per-workspace entries sharing an id with a global one replace it (case-insensitively); the rest are appended in file order.
Presets
A preset is a named, validated configuration patch for one node type. Selection is two-level: preset type, then preset.
preset_types:
- id: tool_nodes
label: Tool nodes
description: Presets for tool nodes
presets:
- id: code-safe-mode
preset_type: tool_nodes
node_type: tools/code
label: Code execution safe mode
description: Restrict file and shell behavior
llm_visible: true
risk_level: high
config_patch:
allow_write: false
sandbox: strict
| Field | Meaning |
|---|---|
id | Stable, file-unique identity used in commands and approval payloads |
preset_type | The category id it belongs to |
node_type | The node type this patch is valid for — applying it elsewhere is rejected |
label | Shown in the selection list |
description | Human- and LLM-facing explanation of what it changes and when to use it |
llm_visible | Whether scoped LLM discovery may surface it — default false |
risk_level | low, medium, or high |
config_patch | JSON object shallow-merged into the target node's config |
The patch is applied through the same validation path as a normal node update, so a preset cannot write a configuration the property panel would have rejected.
One source of truth
presets.yaml is the single store on every surface. The editor's property panel "save as
preset" writes into it, and the chat preset selector, the CLI, and external channels read it
back. There is deliberately no second browser-local preset store to reconcile.
A user-saved preset with no category lands in user_presets ("Saved presets"), created on
demand, and defaults to llm_visible: false — a preset you saved for yourself is not
automatically offered to an agent, because that would widen what an agent can change without
review.
Because persisting one preset means rewriting the whole file, saves are serialised behind a process-local lock; otherwise two concurrent saves would race and the second would drop the first.
LLM discovery is deliberately narrow
An agent does not see the workspace catalog. A preset is discoverable only when it is
llm_visible and its node_type is among the node types reachable as tools from the
requesting node.
And an LLM may request a preset switch but never apply one. The request becomes an approval
carrying the workspace, workflow, requester node, target node, preset id and label, a
change_summary diff, and an optional reason. It rides the normal approval infrastructure —
~~y / ~~n, the CLI, web cards — under the action name preset_switch, so every surface can
tell it apart from a tool-execution approval. See
Approvals & Human Review.
Failure behaviour
Listing fails soft: a missing or malformed presets.yaml logs a warning and yields an empty
catalog, so chat ingestion never crashes because of a bad workspace file.
Applying and saving fail hard, with typed errors: a save that did not happen must never look like it did.
Shortcuts
A shortcut attaches structured routing data to a turn:
shortcuts:
- name: Need Tools
description: Route to the tool-capable branch
route_mode: always
value:
need_tools: true
- name: Search
description: Route to the search action
value:
action:
name: search
- name: Deep Think
description: Enable extended reasoning mode
route_mode: always
value:
reasoning:
mode: deep
max_steps: 20
| Field | Meaning |
|---|---|
name | Display name and stable identity; non-empty, unique in the file |
description | One line shown in the selection list |
route_mode | once (default — next turn only) or always (until cleared) |
value | JSON object merged into the turn's routing payload |
value must be an object, because the consumer is ai/llm_branch, which routes on
nested-path lookups. Prefer nested maps (action: { name: search }) over flat dotted keys
(action.name: search) — the latter will not resolve.
Why not just type it in the message
A shortcut attaches routing data beside the message rather than injecting JSON into the message text. Two consequences: chat history stays clean, and the model never sees route-control JSON as if it were natural language.
Lifetime
The active shortcut is stored in the conversation context under active_shortcut_v1 and
serialised into the turn's route_shortcut input each turn. once clears after one turn;
always persists until the user deselects it — the selector is a toggle.
This is distinct from the pending numbered selection, which is a transient snapshot of a list.
Using them
| Surface | Presets | Shortcuts |
|---|---|---|
| Editor | Property panel selector; "save as preset" | Chat toolbar |
| Chat | Preset selector above the input | Shortcut selector above the input |
| CLI | /presets | /shortcuts |
| External channels | ~~presets, then a number | ~~shortcuts, then a number |
See Channel Routing for the channel command set and Embedded Chat for the toolbar.
Practical advice
- Name shortcuts as intents, not as implementation:
Deep Thinkbeatsset reasoning.mode=deep. - Use
alwayssparingly. It stays on until deselected, and a forgotten sticky shortcut is hard to notice. - Keep
llm_visible: falseby default and open it only for presets that are safe for an agent to request — noting the request still needs approval. - Set
risk_levelon anything that widens access; it drives how the approval is presented. - Put shared entries in the global file and use per-workspace files only for genuine overrides.
Next steps
- Embedded Chat in the Editor — the selectors in the UI.
- Approvals & Human Review — the gate on LLM preset switches.
- Environment Variable Reference — where the workspace root is set.