Skip to main content

Agents & Actions

Action Template Variables

When an action runs, Spruce substitutes a small set of template variables into a mode's args / env (or the legacy top-level command: string) and into the markdown body before launch. The same {{name}} placeholders work in either place.

Variables

VariableResolves to
{{artifact_id}}The artifact's ID (e.g. SPR-abc123)
{{session_id}}The unique ID of the terminal session running this action
{{action_content}}The action's markdown body, used to inject the prompt into a flag like --system-prompt
{{current_time}}Current date and time, ISO 8601
{{artifact_path}}Absolute path to the current artifact's .md file
{{code_path}}Path to the code root: the worktree path when working in a worktree, otherwise the project root
{{project_path}}Path to the project root (the folder that contains .spruce/)
{{template_path}}Path to the templates directory (.spruce/templates/)
{{sidecar_path}}Absolute path to the bundled spruce-cli binary (falls back to "spruce" when the bundled sidecar can't be resolved). Used inside per-mode MCP config snippets so they can reference the CLI without a hard-coded path.
{{session_file:<name>}}Absolute path to a per-session file declared in the mode's session_files: map. The file is written under <tmp>/spruce-sessions/<session_id>/<name> after its own {{placeholder}}s are resolved. Used by agents that take MCP config via a settings file rather than a flag (e.g. Gemini's GEMINI_CLI_SYSTEM_SETTINGS_PATH).
{{template:<type>}}Inline content of a template definition (e.g. {{template:task}})
{{context:<name>}}Inline content from a context file (e.g. {{context:artifact-system}}, {{context:comment-system}})

How {{action_content}} works

{{action_content}} is the canonical way to pass the action's prompt to an agent. Inside a mode's args:, the body lands in whatever flag the agent uses for its system prompt:

yaml

modes:
  claude:
    binary: claude
    args:
      - --system-prompt
      - "{{action_content}}"

After substitution, the spawned argv is ["claude", "--system-prompt", "<the markdown body, with all other variables already substituted>"]. Inside the body, you can still use other variables; they're resolved before the substitution happens, so {{artifact_id}} inside the prompt becomes the actual ID by the time the agent sees it.

How {{sidecar_path}} works

{{sidecar_path}} resolves to the absolute path of the bundled spruce-cli binary (e.g. /Applications/Spruce.app/Contents/MacOS/spruce-cli on macOS). Use it inside per-mode MCP config snippets so the same action works across machines without baking in a hard-coded path:

yaml

modes:
  claude:
    binary: claude
    args:
      - --mcp-config
      - '{"mcpServers":{"spruce":{"command":"{{sidecar_path}}","args":["mcp","serve"]}}}'

If the bundled sidecar can't be resolved (e.g. a dev build), {{sidecar_path}} falls back to the string "spruce", so the snippet still works as long as spruce-cli is on PATH.

How {{session_file:<name>}} works

Some agents only read MCP config from a settings file rather than a CLI flag. For those, a mode can declare a session_files: map whose contents Spruce writes to disk per session — under <tmp>/spruce-sessions/<session_id>/<name> — and {{session_file:<name>}} resolves to that file's path. This is how the bundled Gemini mode wires up MCP:

yaml

modes:
  gemini:
    binary: gemini
    args:
      - --prompt-interactive
      - "{{action_content}} Get artifact {{artifact_id}} and begin."
    session_files:
      gemini_settings:
        content: '{"mcpServers":{"spruce":{"command":"{{sidecar_path}}","args":["mcp","serve"]}}}'
    env:
      GEMINI_CLI_SYSTEM_SETTINGS_PATH: "{{session_file:gemini_settings}}"

Each session file's content: is itself run through {{placeholder}} substitution before being written, so {{sidecar_path}} inside a session-file body resolves the same way it does anywhere else.

How {{context:<name>}} works

Spruce ships a few canned context blocks that explain its primitives to the agent: {{context:artifact-system}} describes the artifact model, {{context:comment-system}} describes the comment system, and so on. These get inlined into the prompt at runtime so the agent has the relevant background without bloating every action's body.

The block names map to files Spruce maintains; you don't define them yourself.

Example: agent action

markdown

---
display:
  icon: list-tree
modes:
  claude:
    binary: claude
    args:
      - --mcp-config
      - '{"mcpServers":{"spruce":{"command":"{{sidecar_path}}","args":["mcp","serve"]}}}'
      - --system-prompt
      - "{{action_content}}"
      - --allowedTools
      - "Read,Glob,Grep,Edit,Write,Bash,mcp__spruce__*"
      - --
      - "Get artifact {{artifact_id}} and begin."
---
You are a software architect.

Current date: {{current_time}}
Session ID: {{session_id}}

{{context:artifact-system}}
{{context:comment-system}}

When run on artifact SPR-abc123 with session sess-42, the agent ends up with the body fully substituted, {{sidecar_path}} pointing at the bundled CLI, and {{artifact_id}} already inlined into the user-turn prompt.

Example: non-agent action

markdown

---
display:
  icon: target
modes:
  shell:
    command: npm test -- --grep "{{artifact_id}}"
---

Run on artifact SPR-abc123 and the command becomes npm test -- --grep "SPR-abc123".

Paths and the worktree

{{code_path}} points to the worktree for the current artifact when one exists (i.e. the artifact has been Start'd). Otherwise it points to the main repo checkout. This is what you want most of the time: actions running against an in-flight artifact stay isolated to that artifact's worktree.