From b1be15c9afd39521f2cdd03fc7d61de8d8edc2fd Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Tue, 2 Jun 2026 09:54:20 -0600 Subject: [PATCH 1/9] docs: updated docs to mention that skills only function if function calling is enabled --- REPL.md | 3 +++ Skills.md | 29 ++++++++++++++++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/REPL.md b/REPL.md index 59e1cd6..4e8cc2a 100644 --- a/REPL.md +++ b/REPL.md @@ -78,6 +78,9 @@ cannot be persisted to a file and saved. Skills are modular knowledge or capability packs the LLM can load and unload mid-conversation. Multiple skills can be loaded at once; their instructions stack and their tools/MCP servers union with the active role/agent/session. +> **Requires function calling.** Skills depend on Coyote's function calling system. If `function_calling_support: false` +> in your global config, the `.skill load` and `.skill unload` commands refuse, and the model cannot load skills itself. + | Command | Description | |------------------------|---------------------------------------------------------------------------------------------------| | `.skill loaded` | List currently-loaded skills in this session | diff --git a/Skills.md b/Skills.md index 9695deb..42ce164 100644 --- a/Skills.md +++ b/Skills.md @@ -11,6 +11,13 @@ Common uses: - **Toolkit unlocks** that grant a small bundle of tools or MCP servers without changing the active role. - **One-shot helpers** that auto-unload after the model finishes a task (see [auto_unload](#auto-unload)). +> **Prerequisite: function calling must be enabled.** Skills are built on top of Coyote's function calling system so as +> to not overwhelm the context with skill descriptions. `skill__list`, `skill__load`, and `skill__unload` meta-tools are +> themselves function calls, and most skills grant tools the model needs to invoke. If your global config has +> `function_calling_support: false`, the entire skills system is disabled: the meta-tools are not registered, the REPL +> `.skill load` command refuses, and `coyote --skill ` exits with a clear error. See [Function Calling](Tools) to +> enable it. + --- # Skill Definition @@ -175,13 +182,19 @@ filtered out of the response and cannot be loaded. `skill__load` rejects it with ## Validation -Three validation points enforce these rules: +Four validation points enforce these rules: -| Where | What is checked | Severity | -|------------------------|---------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------| -| **Config load** | Every name in any `enabled_skills` exists on disk AND is in global `visible_skills` (when `visible_skills` is set). | **Hard error**. Refuses to start with the offending name + level. | -| **Runtime list/build** | For each enabled skill: declared tools require function calling; declared MCPs require MCP support. | **Filter + warn**. Incompatible skills are dropped from `skill__list`. | -| **`skill__load` call** | Skill is in the effective enabled set AND compatible with current feature flags. | **Tool error to model** with a distinct message per failure mode. | +| Where | What is checked | Severity | +|-------------------------|---------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------| +| **Global feature gate** | `function_calling_support: true` is required for skills to exist at all (registry/tools/REPL/CLI). | **Skills are silently disabled**. No `skill__*` tools, `.skill load` and `--skill` refuse. | +| **Config load** | Every name in any `enabled_skills` exists on disk and is in global `visible_skills` (when `visible_skills` is set). | **Hard error**. Refuses to start with the offending name + level. | +| **Runtime list/build** | For each enabled skill: declared MCPs require MCP support. | **Filter + warn**. Incompatible skills are dropped from `skill__list`. | +| **`skill__load` call** | Skill is in the effective enabled set and its declared MCPs are usable. | **Tool error to model** with a distinct message per failure mode. | + +> **Skills require function calling.** Skills are useless without it. The meta-tools (`skill__list`, `skill__load`, +> `skill__unload`) are themselves function calls, and most skills grant tools the model needs to invoke. If +> `function_calling_support: false` in your global config, the entire skills feature is off. The REPL `.skill load` +> command, the CLI `--skill ` flag, and the model's own `skill__load` invocation all refuse with a clear error. --- @@ -274,4 +287,6 @@ When skills are enabled, the model has three tools available for managing them i | `skill__unload` | `name: string` | Unloads a loaded skill. Releases its MCP server handles. | These are how the model discovers and uses skills mid-conversation. You can disable this discovery channel by setting -`skills_enabled: false` at any level; the three tools then disappear from the model's function list entirely. +`skills_enabled: false` at any level; the three tools then disappear from the model's function list entirely. The same +happens if `function_calling_support: false` is set globally. Without function calling, the skill system has no surface +to operate on, so it is silently disabled across REPL, CLI, agents, and the model's tool list. From 2cbefe74e39bd5f95234caed9ef6478ba7a98222 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Tue, 2 Jun 2026 12:45:13 -0600 Subject: [PATCH 2/9] docs: documented graph-based agent llm skills usage --- Graph-Agents.md | 56 +++++++++++++++++++++++++++++++++ Skills.md | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) diff --git a/Graph-Agents.md b/Graph-Agents.md index 9ab02d4..05c528b 100644 --- a/Graph-Agents.md +++ b/Graph-Agents.md @@ -68,6 +68,10 @@ global_tools: # global tools available to nodes - web_search_coyote.sh mcp_servers: # MCP servers available to nodes - pubmed-search +skills_enabled: true # optional; master switch for skills in `llm` nodes +enabled_skills: # optional; the *universe* of skills referenceable by any llm node + - code-review + - git-master conversation_starters: # suggested prompts in the UI - "Research WebAssembly outside of the browser" variables: # optional; agent variables @@ -138,6 +142,13 @@ nodes: write in the same super-step (the validator catches missing reducers at load time). See [Parallel Execution](#parallel-execution) for the full reducer reference and merge semantics. +- **`skills_enabled` / `enabled_skills`:** Optional [skills](Skills) policy + for the graph. `skills_enabled: false` turns skills off for every `llm` + node in the graph regardless of node-level settings. `enabled_skills` is + the *universe*: the set of skill names any `llm` node may reference in + its own `enabled_skills`. Subset-violations are caught by the validator at + load time. See [Skills in Graph Agents](Skills#skills-in-graph-agents) for + the full per-node story. ### `{{initial_prompt}}`: Automatically Seeded @@ -458,6 +469,51 @@ failure message containing one of: `timed out`, `rate limit`, `429`, other error fails immediately without consuming further attempts. The default is `1` (no retries). +### Skills on `llm` nodes + +`llm` nodes are the only place [skills](Skills) attach inside a graph. Two +optional fields, mirroring the same fields on roles and agents: + +```yaml +implement: + type: llm + prompt: "{{plan_summary}}" + tools: [fs_write, fs_patch, fs_cat] + skills_enabled: true # optional; default inherits from graph level + enabled_skills: # optional per-node restriction; must be a subset + - code-review # of graph-level enabled_skills + - verification-gates +``` + +Semantics (discovery-only, no auto-load): + +- **`skills_enabled`:** Per-node override of the skills master switch. + `false` disables skills for this node only: no `skill__list` / + `skill__load` / `skill__unload` meta-tools are exposed to the model. + `true` or omitted inherits the graph-level / agent-level setting. +- **`enabled_skills`:** Per-node restriction of which skills the model can + see and load. The graph-level `enabled_skills` is the universe; the + per-node list must be a subset (the validator catches violations at + load time). When set, `skill__list` shows only these skills and + `skill__load` only accepts these names. +- **The model loads what it needs.** Nothing is auto-loaded. While the + node runs, the model uses `skill__list` to discover what's available + and `skill__load` to bring a skill's body / tools / MCP servers into + scope when it actually needs them. This matches the design intent of + skills as a dynamic capability layer. +- **Per-node policy isolation.** The node temporarily narrows the active + agent's skill policy to the node's `enabled_skills`. The original + policy is restored when the node finishes, so subsequent nodes see + their own policy. Any skills the model loaded during the node persist + into subsequent nodes by default; they're real registry insertions, + not node-scoped state. (Use the skill's own `auto_unload: true` + frontmatter for skills that should clean up automatically at turn + end.) + +Agent nodes (which spawn full sub-agents) intentionally have no +`enabled_skills` field. The spawned child agent's own +`config.yaml`/`graph.yaml` declares its skill policy. + --- ## rag diff --git a/Skills.md b/Skills.md index 42ce164..2d61602 100644 --- a/Skills.md +++ b/Skills.md @@ -198,6 +198,90 @@ Four validation points enforce these rules: --- +# Skills in Graph Agents + +[Graph agents](Graph-Agents) integrate with the skill system at two levels: a graph-wide *universe* set at the top of +`graph.yaml`, and a per-node *auto-load* set on `llm` nodes. Other node types (script, approval, input, rag, map, end) +have no skill surface. Skills only apply where a model call happens. + +## Graph level — the universe + +The top-level `skills_enabled` and `enabled_skills` fields in `graph.yaml` work just like they do on a normal agent's +`config.yaml`: they declare the policy ceiling for the whole graph. + +```yaml +name: coder +skills_enabled: true +enabled_skills: + - code-review + - git-master + - verification-gates +``` + +- **`skills_enabled: false`** at the graph level turns skills off for every `llm` node in the graph regardless of + node-level fields. Equivalent to "skills don't exist for this graph." +- **`enabled_skills`** at the graph level is the *universe*: the set of skill names any `llm` node in the graph is + allowed to reference in its own `enabled_skills`. The validator rejects per-node entries that aren't in this set at + load time. +- Omitting `enabled_skills` inherits whatever the role / global cascade resolves to (typically "all visible"). + +## Per-node: discovery-only on `llm` nodes + +`llm` nodes can independently declare `skills_enabled` and `enabled_skills`. The graph-level field gates what's +*allowed* in the graph at all; the node-level field narrows that universe to what *this* node's model can see and +load. Nothing is auto-loaded. The model uses `skill__list` and `skill__load` to bring skills in as it needs them, +matching the rest of Coyote's skill model. + +```yaml +nodes: + implement: + type: llm + prompt: "{{plan_summary}}" + tools: [fs_write, fs_patch, fs_cat] + enabled_skills: # must be a subset of graph-level enabled_skills + - code-review + - verification-gates + # skills_enabled: false # would disable skills for this node only +``` + +Semantics: + +- **Discovery, not auto-load.** When the node runs, `skill__list` is exposed to the model along with `skill__load` + and `skill__unload`. The model decides which skills (if any) to load based on the task at hand. Loaded skills + compose into the system prompt and union tools/MCP servers via the standard `effective_role` pipeline; the + loading itself goes through the same `refresh_tool_scope` path as `.skill load` in the REPL. +- **Per-node policy.** When `enabled_skills` is set on the node, the graph's effective skill policy is temporarily + narrowed to that subset for the duration of the node. `skill__list` will only show those skills and `skill__load` + will only accept those names. When the node finishes, the original policy is restored so the next node sees its + own. +- **`skills_enabled: false`** at the node level is an off-switch: no `skill__*` meta-tools are exposed and no skills + can be loaded from this node. Useful when one node in an otherwise skill-enabled graph should run with a strict, + narrow context (for example a structured-output extraction step that shouldn't load editorial conventions). +- **Skill state carry-over.** Skills the model loads during a node persist into subsequent nodes (they're real + registry insertions, not node-scoped state). If you want a skill to clean up automatically at turn end, mark it + `auto_unload: true` in the skill's own frontmatter. + +## Agent nodes have no skill surface + +`agent` nodes spawn a full child agent (graph or normal). That child agent declares its own skill policy in its own +`config.yaml` / `graph.yaml`. Adding a skill field on the agent node would be redundant at best and surprising at +worst. The child author already controls what skills the child uses. So agent nodes intentionally don't accept +`enabled_skills` / `skills_enabled`. + +## Validation + +| Where | Check | Severity | +|----------------|------------------------------------------------------------------------------------------------------------|------------| +| Graph load | Per-node `enabled_skills` entries are non-empty strings | Hard error | +| Graph load | Per-node `enabled_skills` are a subset of graph-level `enabled_skills` (when set) | Hard error | +| Node execution | Each auto-loaded skill resolves to a real installed skill (otherwise that skill is skipped with a warning) | Warn | +| Node execution | Each auto-loaded skill's MCP / function-calling requirements are satisfied | Warn | + +The validator only runs `validate_before_run` (the default); inspecting a graph via `coyote --info -a ` does +not trigger validation, so subset-violation errors surface on the first real run. + +--- + # Auto-unload Skills with `auto_unload: true` in their frontmatter are removed from the registry at the end of each turn. Specifically, From 1e8a53c014ca95fb22d3fd0044c2933c42e3ce0d Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Tue, 2 Jun 2026 14:02:27 -0600 Subject: [PATCH 3/9] docs: added docs for the new and improved sisyhpus agent system --- Agents.md | 10 ++++++++-- Graph-Agents.md | 5 +++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Agents.md b/Agents.md index ce73749..470d739 100644 --- a/Agents.md +++ b/Agents.md @@ -736,14 +736,20 @@ available; only the auto-injected prompt text is suppressed. Coyote comes packaged with some useful built-in agents: * `coder`: An agent to assist you with all your coding tasks -* `code-reviewer`: A [CodeRabbit](https://coderabbit.ai)-style code reviewer that spawns per-file reviewers using the teammate messaging pattern +* `code-reviewer`: A [CodeRabbit](https://coderabbit.ai)-style code reviewer that spawns per-file reviewers using the + teammate messaging pattern * `demo`: An example agent to use for reference when learning to create your own agents * `deep-research`: A graph-based agent designed to perform deep web research * `explore`: An agent designed to help you explore and understand your codebase * `file-reviewer`: An agent designed to perform code-review on a single file (used by the `code-reviewer` agent) +* `librarian`: A graph-based agent that researches external references. It finds official docs, production OSS examples, + and web best practices. The "external grep" sibling of `explore` (which handles internal/codebase grep). Designed to + be delegated to by `sisyphus` whenever an unfamiliar library, API, or framework is involved. * `oracle`: An agent for high-level architecture, design decisions, and complex debugging * `report-writer`: An agent to polish research findings into clear, citation-preserving final reports -* `sisyphus`: A powerhouse orchestrator agent for writing complex code and acting as a natural language interface for your codebase (similar to ClaudeCode, Gemini CLI, Codex, or OpenCode). Uses sub-agent spawning to delegate to `explore`, `coder`, and `oracle`. +* `sisyphus`: A powerhouse orchestrator agent for writing complex code and acting as a natural language interface for + your codebase (similar to ClaudeCode, Gemini CLI, Codex, or OpenCode). Uses sub-agent spawning to delegate to + `explore`, `librarian`, `coder`, and `oracle`. * `sql`: A universal SQL agent that enables you to talk to any relational database in natural language Coyote writes these built-in agents to your agents directory on first run and never overwrites them afterward, so any diff --git a/Graph-Agents.md b/Graph-Agents.md index 05c528b..9298358 100644 --- a/Graph-Agents.md +++ b/Graph-Agents.md @@ -1516,6 +1516,11 @@ A short, honest list of things that bite people: - [`graph.example.yaml`](https://github.com/Dark-Alex-17/coyote/blob/main/graph.example.yaml) - A fully-commented, full-featured reference graph agent at the root of the Coyote repository (every top-level field, every node type). +- Built-in graph agents shipped with Coyote: + [`coder`](https://github.com/Dark-Alex-17/coyote/blob/main/assets/agents/coder) (implement -> verify_build -> verify_tests -> self_review -> fix-loop), + [`deep-research`](https://github.com/Dark-Alex-17/coyote/blob/main/assets/agents/deep-research) (the canonical reference that exercises every node type), + [`librarian`](https://github.com/Dark-Alex-17/coyote/blob/main/assets/agents/librarian) (triage -> parallel doc + OSS search -> synthesize -> trim; a compact illustration of static fan-out with reducers). + See [Agents > Built-In Agents](Agents#built-in-agents) for descriptions. - [Agents](Agents) - non-graph agent system (config.yaml + LLM loop) - [Custom Tools](Custom-Tools) - building `tools.sh` / `tools.py` / `tools.ts` files for use in graph nodes From e768fa448ba77a395996beaefbe74142c6ec377b Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 3 Jun 2026 09:01:06 -0600 Subject: [PATCH 4/9] docs: Added documentation for the improved vault implementation --- Getting-Started.md | 5 +- Sharing-Configurations.md | 7 +- Vault.md | 139 ++++++++++++++++++++++++++++++-------- 3 files changed, 119 insertions(+), 32 deletions(-) diff --git a/Getting-Started.md b/Getting-Started.md index 71060ec..6d3967b 100644 --- a/Getting-Started.md +++ b/Getting-Started.md @@ -4,8 +4,9 @@ After installation, you can generate the configuration files and directories by coyote --info ``` -Then, you need to set up the Coyote vault by creating a vault password file. Coyote will do this for you automatically and -guide you through the process when you first attempt to access the vault. So, to get started, you can run: +Then, you need to set up the Coyote vault. On your first run, Coyote walks you through choosing a secrets provider +(Local, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, gopass, or 1Password) and configuring it. See the +[vault documentation](Vault) for the full list of providers and configuration details. To get started, you can run: ```sh coyote --list-secrets diff --git a/Sharing-Configurations.md b/Sharing-Configurations.md index 63515ed..9a6c075 100644 --- a/Sharing-Configurations.md +++ b/Sharing-Configurations.md @@ -197,7 +197,8 @@ MCP server entries (and other config files) can reference vault secrets with `{{ install completes, Coyote scans the resulting `mcp.json` for placeholders that are not yet in your vault and either: - **In a TTY:** prompts you, one secret at a time, whether to add it to the vault now. On the first "Yes", Coyote - initializes the vault password file (if needed). On "No", the secret is deferred and reported at the end. + initializes the vault (if needed; for the Local provider, this just means creating the password file). On "No", the + secret is deferred and reported at the end. - **In a non-TTY environment:** skips prompts entirely; lists every missing secret in a final reminder block, with the commands you can run later (`coyote --add-secret ` or `.vault add `). @@ -249,8 +250,8 @@ asset type plus a `README` describing the customization workflow. The following are **intentionally** outside the install-remote feature's scope: -- **`config.yaml`** (the global Coyote config). It holds user-specific things like editor preference, vault password file - path, client API keys, OAuth tokens, and similar. Merging a shared `config.yaml` would risk breaking auth or routing +- **`config.yaml`** (the global Coyote config). It holds user-specific things like editor preference, secrets provider + configuration, client API keys, OAuth tokens, and similar. Merging a shared `config.yaml` would risk breaking auth or routing traffic somewhere unexpected. Settings that genuinely benefit from sharing (like default models) already belong inside individual agents' or roles' configs. - **Sessions, RAGs, agent runtime data.** These accumulate as you use Coyote and aren't shareable in a meaningful way. diff --git a/Vault.md b/Vault.md index 460768a..216cef1 100644 --- a/Vault.md +++ b/Vault.md @@ -1,15 +1,17 @@ The Coyote vault lets users store sensitive secrets and credentials securely so that there's no plaintext secrets -anywhere in your configurations. +anywhere in your configurations. -It's based on the [G-Man library](https://github.com/Dark-Alex-17/gman) (which also comes in a binary format) which -functions as a universal secret management tool. +It's built on the [G-Man library](https://github.com/Dark-Alex-17/gman), which supports multiple secrets providers: +a local encrypted file, AWS Secrets Manager, Google Cloud Secret Manager, Azure Key Vault, gopass, and 1Password. You +pick the one that fits your workflow and Coyote handles the rest. ![Vault Demo](./images/vault/vault-demo.gif) --- # Usage -The Coyote vault can be used in one of two ways: via the CLI or via the REPL for interactive usage. +The Coyote vault can be used in one of two ways: via the CLI or via the REPL for interactive usage. The same commands +work regardless of which provider you've configured. ## CLI Usage The vault is utilized from the CLI with the following flags: @@ -33,6 +35,94 @@ The vault can be accessed from within the Coyote REPL using the `.vault` command The manipulation of your vault is guided in the same way as the CLI usage, ensuring ease of use. +# Supported Providers +Coyote supports six secrets providers via [G-Man](https://github.com/Dark-Alex-17/gman). The default is **Local** (an encrypted file on this machine), but you +can switch to any of the others. + +| Provider | Storage | What it needs | +|-----------------------|---------------------------------------------------------------|---------------------------------------------------------------| +| `local` (default) | Encrypted file at `vault.yml` in your Coyote config directory | A password file you create on first run | +| `aws_secrets_manager` | AWS Secrets Manager | An authenticated AWS CLI (`aws sso login` or `aws configure`) | +| `gcp_secret_manager` | Google Cloud Secret Manager | `gcloud auth application-default login` | +| `azure_key_vault` | Azure Key Vault | `az login` | +| `gopass` | The `gopass` password manager | The `gopass` CLI installed and initialized | +| `one_password` | 1Password | The `op` CLI installed and signed in (`op signin`) | + +If you're not logged into the relevant CLI when Coyote needs to read a secret, you'll get an auth error with the +canonical login command. Coyote does not try to log you in automatically. + +# Configuration +There are two ways to configure your secrets provider in `config.yaml`: + +## Shorthand: `vault_password_file` +If all you want is the default Local provider, just set the path to a password file: + +```yaml +vault_password_file: ~/.coyote_password +``` + +This is shorthand for "use the Local provider with this password file". It's the simplest setup if you don't +want to use a dedicated secrets provider external to Coyote. + +## Explicit: `secrets_provider` +For any non-Local provider (or if you want to be explicit about your Local setup), use the `secrets_provider` block: + +```yaml +# Local +secrets_provider: + type: local + password_file: ~/.coyote_password + +# AWS Secrets Manager +secrets_provider: + type: aws_secrets_manager + aws_profile: default + aws_region: us-east-1 + +# Google Cloud Secret Manager +secrets_provider: + type: gcp_secret_manager + gcp_project_id: my-project-id + +# Azure Key Vault +secrets_provider: + type: azure_key_vault + vault_name: my-vault-name + +# gopass +secrets_provider: + type: gopass + store: my-store # Optional; omit to use the default store + +# 1Password +secrets_provider: + type: one_password + vault: Production # Optional; omit to use the default vault + account: my.1password.com # Optional; omit to use the default account +``` + +When `secrets_provider` is set, the legacy `vault_password_file` field is ignored. + +> ⚠️ **Important:** The `secrets_provider` block itself cannot use `{{SECRET_NAME}}` interpolation. Coyote needs to +> initialize the vault *before* it can resolve any secrets, so the provider's own configuration must be literal values. +> All *other* fields in your config (API keys, MCP server env vars, agent variables, etc.) support `{{SECRET_NAME}}` +> references as normal. + +# First-Run Setup +The first time you start Coyote without a config file, a wizard walks you through picking a secrets provider: + +1. Choose a provider from the menu (Local, AWS, GCP, Azure, gopass, 1Password). +2. Coyote prompts you for the provider-specific config (AWS profile/region, GCP project ID, Azure vault name, etc.). +3. For non-Local providers, Coyote performs a **round-trip validation**: it writes a probe secret to the backend, reads + it back, then deletes it. If your credentials don't have the right permissions, or if you're not logged in, Coyote + bails out *before* you fill out the rest of the wizard, with a hint pointing to the correct login command. +4. For the Local provider, Coyote prompts you to create a password file. + +Once the provider is set up, the wizard continues with your LLM/API provider selection and writes your `config.yaml`. + +If you set up Coyote with one provider and later want to switch, just edit your `config.yaml` to change (or add) the +`secrets_provider` block. + # Motivation Coyote is intended to be highly configurable and adaptable to many different use cases. This means that users of Coyote should be able to share configurations for agents, tools, roles, etc. with other users or even entire teams. @@ -40,29 +130,24 @@ should be able to share configurations for agents, tools, roles, etc. with other My objective is to encourage this, and to make it so that users can easily version their configurations using version control. Good VCS hygiene dictates that one *never* commits secrets or sensitive information to a repository. -Since a number of files and configurations in Coyote may contain sensitive information, the vault exists to solve this problem. +Since a number of files and configurations in Coyote may contain sensitive information, the vault exists to solve this +problem. How you share secrets across a team depends on your provider: -Users can either share the vault password with a team, making it so a single configuration can be pulled from VCS and used -by said team. Alternatively, each user can maintain their own vault password and expect other users to replace secret values -with their user-specific secrets. +- **Local:** Either share the vault password with the team (one config + one shared password file) or have each user + maintain their own password and substitute their own secret values. +- **AWS / GCP / Azure / gopass / 1Password:** Each team member uses their own credentials against the shared backend. + The vault becomes a natural single source of truth. Rotating a secret in one place propagates to everyone using + that config. -# How it works -When you first start Coyote, if you don't already have a vault password file, it will prompt you to create one. This file -houses the password that is used to encrypt and decrypt secrets within Coyote. This file exists so that you are not prompted -for a password every time Coyote attempts to decrypt a secret. - -When you encrypt a secret, it uses the local provider for `gman` to securely store those secrets in the Coyote vault file. -This file is typically located at your Coyote configuration directory under `vault.yml`. If you open this file, you'll see a -bunch of gibberish. This is because all secrets are encrypted using the password you provided, meaning only you can decrypt them. - -Secrets are specified in Coyote configurations using the same variable templating as the [Jinja templating engine](https://jinja.palletsprojects.com/en/stable/): +# Referencing Secrets +Secrets are referenced in Coyote configurations using the same variable templating as the [Jinja templating engine](https://jinja.palletsprojects.com/en/stable/): ``` {{some_variable}} ``` -So whenever you want Coyote to use a secret from the vault, you simply specify the secret name in this format in the applicable -file. +So whenever you want Coyote to use a secret from the vault, you simply specify the secret name in this format in the +applicable file. The same syntax works regardless of which provider stores the secret. **Example:** Suppose my vault has a secret called `GITHUB_TOKEN` in it, and I want to use that in the MCP configuration. Then, I simply replace @@ -93,16 +178,17 @@ the expected value in my `mcp.json` with the templated secret: } ``` -At runtime, Coyote will detect the templated secret and replace it with the decrypted value from the vault before executing. +At runtime, Coyote will detect the templated secret and replace it with the decrypted value from the vault before +executing. # Supported Files At the time of writing, the following files support Coyote secret injection: -| File Type | Description | Limitations | -|-------------------------|-----------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------| -| `config.yaml` | The main Coyote configuration file | Cannot use secret injection on the `vault_password_file` field | -| `functions/mcp.json` | The MCP server configuration file | | -| `/tools.` | Tool files for agents | Specific configuration and only supported for Agents, not all global tools ([see below](#environment-variable-secret-injection-in-agents)) | +| File Type | Description | Limitations | +|-------------------------|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------| +| `config.yaml` | The main Coyote configuration file | Cannot use secret injection on the `vault_password_file` field or anywhere inside the `secrets_provider` block | +| `functions/mcp.json` | The MCP server configuration file | | +| `/tools.` | Tool files for agents | Specific configuration and only supported for Agents, not all global tools ([see below](#environment-variable-secret-injection-in-agents)) | Note that all paths are relative to the Coyote configuration directory. The directory varies by system, so you can find yours by @@ -146,4 +232,3 @@ follows: ``` For more information about variable usage within agents, refer to the [Variables section](Agents#user-defined-variables) of the [Agents documentation](Agents) - From 9e1ad70b0873dc201c6d7eb7f82bce2f2c861bc8 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 3 Jun 2026 12:27:22 -0600 Subject: [PATCH 5/9] docs: updated enabled_skills docs to support both CSVs and lists --- REPL.md | 40 +++++++++++++++++++++------------------- Roles.md | 2 ++ Sessions.md | 14 ++++++++------ Skills.md | 30 +++++++++++++++++++----------- 4 files changed, 50 insertions(+), 36 deletions(-) diff --git a/REPL.md b/REPL.md index 4e8cc2a..9a54e25 100644 --- a/REPL.md +++ b/REPL.md @@ -209,25 +209,27 @@ file. The following settings can be adjusted at runtime: -| Setting | Type | Description | -|----------------------------|---------|--------------------------------------------------------------------------| -| `auto_continue` | boolean | Enable/disable the [Todo System](TODO-System) auto-continuation | -| `max_auto_continues` | integer | Maximum number of automatic continuations | -| `inject_todo_instructions` | boolean | Inject default todo instructions into the system prompt | -| `continuation_prompt` | string | Custom continuation prompt (supports multi-word values; `null` to reset) | -| `temperature` | float | Model temperature parameter | -| `top_p` | float | Model top-p parameter | -| `enabled_tools` | string | Comma-separated list of enabled tools | -| `enabled_mcp_servers` | string | Comma-separated list of enabled MCP servers | -| `save_session` | boolean | Whether to auto-save sessions | -| `compression_threshold` | integer | Token threshold for session compression | -| `max_output_tokens` | integer | Maximum output tokens for the current model | -| `dry_run` | boolean | Enable/disable dry run mode | -| `function_calling_support` | boolean | Enable/disable function calling | -| `mcp_server_support` | boolean | Enable/disable MCP server support | -| `stream` | boolean | Enable/disable streaming | -| `save` | boolean | Enable/disable saving responses | -| `highlight` | boolean | Enable/disable syntax highlighting | +| Setting | Type | Description | +|----------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `auto_continue` | boolean | Enable/disable the [Todo System](TODO-System) auto-continuation | +| `max_auto_continues` | integer | Maximum number of automatic continuations | +| `inject_todo_instructions` | boolean | Inject default todo instructions into the system prompt | +| `continuation_prompt` | string | Custom continuation prompt (supports multi-word values; `null` to reset) | +| `temperature` | float | Model temperature parameter | +| `top_p` | float | Model top-p parameter | +| `enabled_tools` | string | Comma-separated list of enabled tools | +| `enabled_mcp_servers` | string | Comma-separated list of enabled MCP servers | +| `enabled_skills` | string | Comma-separated list of enabled [skills](Skills) (e.g. `git-master,ai-slop-remover`); `null` clears the override | +| `save_session` | boolean | Whether to auto-save sessions | +| `compression_threshold` | integer | Token threshold for session compression | +| `max_output_tokens` | integer | Maximum output tokens for the current model | +| `dry_run` | boolean | Enable/disable dry run mode | +| `function_calling_support` | boolean | Enable/disable function calling | +| `mcp_server_support` | boolean | Enable/disable MCP server support | +| `skills_enabled` | boolean | Master switch for [skills](Skills). Accepts `true`, `false`, or `null` (inside a session, `null` clears the session-level override; otherwise resets to the default `true`) | +| `stream` | boolean | Enable/disable streaming | +| `save` | boolean | Enable/disable saving responses | +| `highlight` | boolean | Enable/disable syntax highlighting | ![set](./images/repl/set.gif) diff --git a/Roles.md b/Roles.md index 9f83925..232f599 100644 --- a/Roles.md +++ b/Roles.md @@ -58,6 +58,8 @@ The following table lists the available configuration settings and their default | `top_p` | Default `top_p` for the preferred model | Alternative way to control the model's output diversity, affecting the
probability distribution of tokens | | `enabled_tools` | Global setting for `enabled_tools` | The tools that this role utilizes | | `enabled_mcp_servers` | Global setting for `enabled_mcp_servers` | The MCP servers that this role utilizes | +| `skills_enabled` | Global setting for `skills_enabled` | Master switch for [skills](Skills) under this role. Set to `false` to hide all skills | +| `enabled_skills` | Global setting for `enabled_skills` | The [skills](Skills) this role activates. Accepts either a YAML list or a comma-separated string | | `auto_continue` | Global setting for `auto_continue` | Enable the [Todo System](TODO-System) auto-continuation for this role | | `max_auto_continues` | Global setting for `max_auto_continues` | Maximum number of automatic continuations before stopping | | `inject_todo_instructions` | Global setting for `inject_todo_instructions` | Inject default todo tool usage instructions into the system prompt | diff --git a/Sessions.md b/Sessions.md index 2999333..b588ca6 100644 --- a/Sessions.md +++ b/Sessions.md @@ -117,11 +117,13 @@ The following settings are available to customize the default behavior of sessio In addition to the global settings above, individual sessions can override the following settings. These can be set at runtime using the `.set` command or configured in the session's YAML file: -| Setting | Default | Description | -|----------------------------|-----------------------------------------|---------------------------------------------------------------------------------------------------------------| -| `auto_continue` | Global `auto_continue` value | Enable the [Todo System](TODO-System) auto-continuation for this session. Overrides global and role settings. | -| `max_auto_continues` | Global `max_auto_continues` value | Maximum number of automatic continuations before stopping. Overrides global and role settings. | -| `inject_todo_instructions` | Global `inject_todo_instructions` value | Inject default todo tool usage instructions into the system prompt. Overrides global and role settings. | -| `continuation_prompt` | Global `continuation_prompt` value | Custom prompt used when auto-continuing. Overrides global and role settings. | +| Setting | Default | Description | +|----------------------------|-----------------------------------------|---------------------------------------------------------------------------------------------------------------------------------| +| `auto_continue` | Global `auto_continue` value | Enable the [Todo System](TODO-System) auto-continuation for this session. Overrides global and role settings. | +| `max_auto_continues` | Global `max_auto_continues` value | Maximum number of automatic continuations before stopping. Overrides global and role settings. | +| `inject_todo_instructions` | Global `inject_todo_instructions` value | Inject default todo tool usage instructions into the system prompt. Overrides global and role settings. | +| `continuation_prompt` | Global `continuation_prompt` value | Custom prompt used when auto-continuing. Overrides global and role settings. | +| `skills_enabled` | Global `skills_enabled` value | Master switch for [skills](Skills) in this session. `.set skills_enabled null` clears the session override. | +| `enabled_skills` | Inherited via the skill cascade | The active [skills](Skills) for this session. Accepts either a YAML list or a comma-separated string in the saved session YAML. | For more information on the Todo System, see the [Todo System documentation](TODO-System). diff --git a/Skills.md b/Skills.md index 2d61602..09f30a9 100644 --- a/Skills.md +++ b/Skills.md @@ -106,16 +106,18 @@ your current context with git knowledge, frontend conventions, or any other modu ## REPL commands -| Command | Effect | -|---------------------------------------------|--------------------------------------------------------------------------------------------------| -| `.skill loaded` | List currently-loaded skills in this session. | -| `.skill load ` | Load a skill. Validates policy + compatibility, then refreshes the tool scope. | -| `.skill unload ` | Unload a loaded skill. Releases any MCP servers it pulled in. | -| `.edit skill ` | Open an existing skill in `$EDITOR` (fails if the skill doesn't exist). | -| `.skill ` | Open the skill in `$EDITOR`. Creates a scaffolded `SKILL.md` if missing. | -| `.delete skill` | Interactive prompt to choose installed skills to delete. | -| `.install skills` | Reinstall all bundled built-in skills, overwriting existing copies after confirmation. | -| `.install remote --filter skills` | Install only `skills/` from a remote repo. See [Sharing Configurations](Sharing-Configurations). | +| Command | Effect | +|---------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `.skill loaded` | List currently-loaded skills in this session. | +| `.skill load ` | Load a skill. Validates policy + compatibility, then refreshes the tool scope. | +| `.skill unload ` | Unload a loaded skill. Releases any MCP servers it pulled in. | +| `.edit skill ` | Open an existing skill in `$EDITOR` (fails if the skill doesn't exist). | +| `.skill ` | Open the skill in `$EDITOR`. Creates a scaffolded `SKILL.md` if missing. | +| `.delete skill` | Interactive prompt to choose installed skills to delete. | +| `.install skills` | Reinstall all bundled built-in skills, overwriting existing copies after confirmation. | +| `.install remote --filter skills` | Install only `skills/` from a remote repo. See [Sharing Configurations](Sharing-Configurations). | +| `.set skills_enabled ` | Flip the master switch at runtime. Inside a session this sets the session override; otherwise it updates the global default (`null` restores the default `true`). | +| `.set enabled_skills ` | Replace the global default-active skill list at runtime, e.g. `.set enabled_skills git-master,ai-slop-remover`. Use `null` to clear. | ## CLI flags @@ -172,11 +174,17 @@ Example role that opts into a different skill set than the global default: ```markdown --- name: frontend-dev -enabled_skills: git-master, ai-slop-remover, frontend-ui-ux +enabled_skills: + - git-master + - ai-slop-remover + - frontend-ui-ux --- You are a frontend specialist. ``` +`enabled_skills` accepts either a YAML list (as shown above) or a comma-separated string +(`enabled_skills: git-master,ai-slop-remover,frontend-ui-ux`). Both forms parse to the same value. + When this role is active, the model sees exactly those three skills in `skill__list`. Any other installed skill is filtered out of the response and cannot be loaded. `skill__load` rejects it with `"Skill 'X' is not enabled in this context"`. From de0a051002bfee95c3eb8abbe258cd0390f17cf9 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 3 Jun 2026 13:23:09 -0600 Subject: [PATCH 6/9] docs: updated docs to mention support for enabled_mcp_servers to be either a YAML list or a CSV string --- MCP-Servers.md | 14 ++++++++++---- REPL.md | 2 +- Roles.md | 2 +- Skills.md | 12 ++++++------ 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/MCP-Servers.md b/MCP-Servers.md index 9b55703..525225e 100644 --- a/MCP-Servers.md +++ b/MCP-Servers.md @@ -170,11 +170,16 @@ The following settings are available in the global configuration for MCP servers mcp_server_support: true # Enables or disables MCP server support (globally). mapping_mcp_servers: # Alias for an MCP server or set of servers git: github,gitmcp -enabled_mcp_servers: null # Which MCP servers to enable by default (e.g. 'github,slack') +enabled_mcp_servers: null # Which MCP servers to enable by default. + # Accepts either a YAML list or a comma-separated string. Examples: + # enabled_mcp_servers: github,slack + # enabled_mcp_servers: + # - github + # - slack ``` -A special note about `enabled_mcp_servers`: a user can set this to `all` to enable all configured MCP servers in the -`functions/mcp.json` configuration. +A special note about `enabled_mcp_servers`: a user can set this to `all` (or include `all` in the list) to enable all +configured MCP servers in the `functions/mcp.json` configuration. (See the [Configuration Example](https://github.com/Dark-Alex-17/coyote/blob/main/config.example.yaml) file for an example global configuration with all options.) @@ -187,7 +192,8 @@ When running in REPL-mode, the `mcp_server_support` and `enabled_mcp_servers` se When you create a role, you have the following MCP-related configuration options available to you: ```yaml -enabled_mcp_servers: github # Which MCP servers the role uses. +enabled_mcp_servers: # Which MCP servers the role uses. Accepts either a YAML list (as shown) + - github # or a comma-separated string (e.g. `enabled_mcp_servers: github,slack`). ``` The values for `mapping_mcp_servers` are inherited from the `[global configuration](#global-configuration)`. diff --git a/REPL.md b/REPL.md index 9a54e25..54c22b5 100644 --- a/REPL.md +++ b/REPL.md @@ -218,7 +218,7 @@ The following settings can be adjusted at runtime: | `temperature` | float | Model temperature parameter | | `top_p` | float | Model top-p parameter | | `enabled_tools` | string | Comma-separated list of enabled tools | -| `enabled_mcp_servers` | string | Comma-separated list of enabled MCP servers | +| `enabled_mcp_servers` | string | Comma-separated list of enabled MCP servers (e.g. `github,slack` or `all`); the saved YAML config also accepts a list form | | `enabled_skills` | string | Comma-separated list of enabled [skills](Skills) (e.g. `git-master,ai-slop-remover`); `null` clears the override | | `save_session` | boolean | Whether to auto-save sessions | | `compression_threshold` | integer | Token threshold for session compression | diff --git a/Roles.md b/Roles.md index 232f599..ea9efa5 100644 --- a/Roles.md +++ b/Roles.md @@ -57,7 +57,7 @@ The following table lists the available configuration settings and their default | `temperature` | Default `temperature` for the preferred model | Controls the creativity and randomness of the model's responses | | `top_p` | Default `top_p` for the preferred model | Alternative way to control the model's output diversity, affecting the
probability distribution of tokens | | `enabled_tools` | Global setting for `enabled_tools` | The tools that this role utilizes | -| `enabled_mcp_servers` | Global setting for `enabled_mcp_servers` | The MCP servers that this role utilizes | +| `enabled_mcp_servers` | Global setting for `enabled_mcp_servers` | The MCP servers that this role utilizes. Accepts either a YAML list or a comma-separated string | | `skills_enabled` | Global setting for `skills_enabled` | Master switch for [skills](Skills) under this role. Set to `false` to hide all skills | | `enabled_skills` | Global setting for `enabled_skills` | The [skills](Skills) this role activates. Accepts either a YAML list or a comma-separated string | | `auto_continue` | Global setting for `auto_continue` | Enable the [Todo System](TODO-System) auto-continuation for this role | diff --git a/Skills.md b/Skills.md index 09f30a9..f677c15 100644 --- a/Skills.md +++ b/Skills.md @@ -68,12 +68,12 @@ To see complete examples, look at the [bundled built-in skills](https://github.c The YAML frontmatter at the top of `SKILL.md` is where you declare the skill's metadata and what extra capabilities it grants when loaded. All fields are optional. -| Field | Default | Description | -|-----------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `description` | empty | Short one-line description shown to the model when it lists available skills. Make it specific: this is what helps the model decide when to load. | -| `enabled_tools` | none | Comma-separated tool names that become available while the skill is loaded. Union with the active role/agent/session's tools. Tools must exist in [visible tools](Tools). | -| `enabled_mcp_servers` | none | Comma-separated MCP server names. Skills can reference servers from your `mcp.json`; those servers are auto-acquired on load and released on unload via reference counting. | -| `auto_unload` | `false` | If `true`, the skill is automatically removed from the registry at the end of every turn where the model produced a final response (no more tool calls). | +| Field | Default | Description | +|-----------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `description` | empty | Short one-line description shown to the model when it lists available skills. Make it specific: this is what helps the model decide when to load. | +| `enabled_tools` | none | Comma-separated tool names that become available while the skill is loaded. Union with the active role/agent/session's tools. Tools must exist in [visible tools](Tools). | +| `enabled_mcp_servers` | none | MCP server names the skill needs. Accepts a YAML list (preferred) or a comma-separated string. Skills can reference servers from your `mcp.json`; those servers are auto-acquired on load and released on unload via reference counting. | +| `auto_unload` | `false` | If `true`, the skill is automatically removed from the registry at the end of every turn where the model produced a final response (no more tool calls). | ## Body From ddc8ae085f04b2809f232f98ca5fa3a32aed432c Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 3 Jun 2026 14:22:15 -0600 Subject: [PATCH 7/9] docs: added documentation for the enabled_tools CSV or list forms --- REPL.md | 2 +- Roles.md | 2 +- Tools.md | 12 +++++++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/REPL.md b/REPL.md index 54c22b5..b6ae52d 100644 --- a/REPL.md +++ b/REPL.md @@ -217,7 +217,7 @@ The following settings can be adjusted at runtime: | `continuation_prompt` | string | Custom continuation prompt (supports multi-word values; `null` to reset) | | `temperature` | float | Model temperature parameter | | `top_p` | float | Model top-p parameter | -| `enabled_tools` | string | Comma-separated list of enabled tools | +| `enabled_tools` | string | Comma-separated list of enabled tools (e.g. `fs_ls,fs_cat` or `all`); the saved YAML config also accepts a list form | | `enabled_mcp_servers` | string | Comma-separated list of enabled MCP servers (e.g. `github,slack` or `all`); the saved YAML config also accepts a list form | | `enabled_skills` | string | Comma-separated list of enabled [skills](Skills) (e.g. `git-master,ai-slop-remover`); `null` clears the override | | `save_session` | boolean | Whether to auto-save sessions | diff --git a/Roles.md b/Roles.md index ea9efa5..eafc824 100644 --- a/Roles.md +++ b/Roles.md @@ -56,7 +56,7 @@ The following table lists the available configuration settings and their default | `model` | Default configured model or currently in-use model (REPL mode) | The preferred model to use with this role | | `temperature` | Default `temperature` for the preferred model | Controls the creativity and randomness of the model's responses | | `top_p` | Default `top_p` for the preferred model | Alternative way to control the model's output diversity, affecting the
probability distribution of tokens | -| `enabled_tools` | Global setting for `enabled_tools` | The tools that this role utilizes | +| `enabled_tools` | Global setting for `enabled_tools` | The tools that this role utilizes. Accepts either a YAML list or a comma-separated string | | `enabled_mcp_servers` | Global setting for `enabled_mcp_servers` | The MCP servers that this role utilizes. Accepts either a YAML list or a comma-separated string | | `skills_enabled` | Global setting for `skills_enabled` | Master switch for [skills](Skills) under this role. Set to `false` to hide all skills | | `enabled_skills` | Global setting for `enabled_skills` | The [skills](Skills) this role activates. Accepts either a YAML list or a comma-separated string | diff --git a/Tools.md b/Tools.md index 99aa395..58079db 100644 --- a/Tools.md +++ b/Tools.md @@ -74,7 +74,12 @@ The following settings are available in the global configuration for tools: function_calling_support: true # Enables or disables function calling in any context mapping_tools: # Alias for a tool or toolset fs: 'fs_cat,fs_ls,fs_mkdir,fs_rm,fs_write' -enabled_tools: null # Which tools to use by default. (e.g. 'fs,web_search_coyote') +enabled_tools: null # Which tools to use by default. + # Accepts either a YAML list or a comma-separated string. Examples: + # enabled_tools: fs,web_search_coyote + # enabled_tools: + # - fs + # - web_search_coyote visible_tools: # Which tools are visible to be compiled (and are thus able to be defined in 'enabled_tools') # - demo_py.py - execute_command.sh @@ -113,7 +118,8 @@ context. When you create a role, you have the following global tool-related configuration options available to you: ```yaml -enabled_tools: query_jira_issues # Which tools the role uses. +enabled_tools: # Which tools the role uses. Accepts a YAML list (as shown) + - web_search_coyote # or a comma-separated string (e.g. `enabled_tools: web_search_coyote`). ``` The values for `mapping_tools` are inherited from the [global configuration](#global-configuration). @@ -125,7 +131,7 @@ When you create an agent, you have the following global tool-related configurati ```yaml global_tools: # Which global tools the agent uses - - query_jira_issues.sh + - web_search_coyote.sh - fs_cat.sh - fs_ls.sh ``` From 3bbe8f4b3de84ef131ab69a3e5191b562dc35e44 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 3 Jun 2026 14:22:31 -0600 Subject: [PATCH 8/9] docs: Added docs on the new LLM_TOOL_RAW_JSON escape hatch --- Custom-Bash-Tools.md | 54 ++++++++++++++++++++++++++++++++++++++++ Custom-Tools.md | 59 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 107 insertions(+), 6 deletions(-) diff --git a/Custom-Bash-Tools.md b/Custom-Bash-Tools.md index b138d81..a8d29fc 100644 --- a/Custom-Bash-Tools.md +++ b/Custom-Bash-Tools.md @@ -284,6 +284,60 @@ $ ./get_current_time.sh Fri Oct 24 05:55:04 PM MDT 2025 ``` +# Handling large or special-character argument values + +Coyote dispatches a tool call by converting the LLM's JSON arguments into shell `--option=` flags via `jq`, then +`eval`-ing the result. The flag values reach your `main` function as `argc_*` variables. For most calls this works fine. + +However, for **very large values, multi-line values dense with newlines, or values with many markdown table pipes (`|`), +single quotes, em-dashes, and other shell-significant characters**, the shell-quoting round-trip can occasionally drop +characters or truncate the value before it reaches your `argc_*` variable. Symptoms include `argc_*` being shorter than +what the LLM sent, or starting mid-content. + +## The escape hatch: `LLM_TOOL_RAW_JSON` + +Coyote exports the raw JSON envelope it received from the LLM as the `LLM_TOOL_RAW_JSON` environment variable on every +tool invocation. To bypass argc parsing for a specific option, re-derive its value directly from the JSON using `jq`: + +```bash +# Prefer the raw JSON when available, fall back to argc parsing if not +if [[ -n "$LLM_TOOL_RAW_JSON" ]] && command -v jq >/dev/null 2>&1; then + argc_contents="$(jq -r '.contents' <<< "$LLM_TOOL_RAW_JSON")" + argc_path="$(jq -r '.path' <<< "$LLM_TOOL_RAW_JSON")" +fi +``` + +The `jq -r` ("raw") flag preserves every byte of the original LLM-sent value, including newlines, quotes, em-dashes, +and shell-special characters, without any shell-quoting layer in between. This is the same approach Coyote's bundled +`fs_write`, `fs_patch`, `execute_command`, `execute_sql_code`, and `send_mail` tools use for their large-payload options. + +The fallback (`fall back to argc parsing if not`) is intentional: if `LLM_TOOL_RAW_JSON` is unset or `jq` isn't +installed, the tool still works via the standard argc path. You're adding a more reliable code path, not replacing the +existing one. + +## When to use this + +- Your option's value can legitimately be many KB of text (file contents, code, email bodies, SQL queries). +- Your option's value can contain shell-significant characters in dense patterns (pipes, single quotes, ANSI escapes). +- You observe that `argc_