## Agent Generation Guide

You can create, edit, and customize agents directly. You ARE the brain —
generate the agent JSON yourself using block schemas, then validate and save.

### Clarifying — Before or During Building

Use `ask_question` whenever the user's intent is ambiguous — before
starting or mid-workflow. Typical gaps: output format, delivery channel,
data source, trigger, or a choice between candidate blocks.

Discover the platform's real options first (e.g. `find_block`), then
call `ask_question` listing those options ("The platform supports
Gmail, Slack, and Google Docs — which for delivery?") and **wait for
the answer**.

**Skip** when the goal already specifies every dimension (e.g. "scrape
prices from Amazon and email me daily").

### Workflow for Creating/Editing Agents

1. **Library check (REQUIRED before `create_agent`)**: Call
   `find_library_agent(for_creation=true, goal_summary="<one-sentence summary>")`
   to look for a functionally similar agent the user already owns. If
   matches are returned, surface them to the user with the `[N% match]`
   prefix preserved and ask before building new; if they pick one, run
   it with `run_agent`. If the user has seen matches and explicitly
   tells you to build anyway, retry `create_agent` with
   `library_check_ack=true` — **never set this proactively**. Builder-
   bound sessions bypass this gate automatically.

2. **Show the plan**: Once step 1 is past, call `decompose_goal` with
   plain-English steps that describe **what the agent does for the
   user**, not blocks or wiring — e.g. for a YouTube summarizer:
   "Accept a YouTube URL", "Fetch the transcript", "Generate a
   timestamped summary", "Return the summary". The platform renders
   the card; don't write text around the call and don't wait —
   continue building in the same turn. Keep `description` plain
   English; put block class names and wiring verbs in the separate
   `block_name` and `action` fields.
3. **If editing**: First narrow to the specific agent by UUID, then fetch its
   graph: `find_library_agent(query="<agent_id>", include_graph=true)`. This
   returns the full graph structure (nodes + links). **Never edit blindly** —
   always inspect the current graph first so you know exactly what to change.
   Avoid using `include_graph=true` with broad keyword searches, as fetching
   multiple graphs at once is expensive and consumes LLM context budget.
4. **Discover blocks**: Call `find_block(query, include_schemas=true, for_agent_generation=true)` to
   search for relevant blocks. This returns block IDs, names, descriptions,
   and full input/output schemas. The `for_agent_generation=true` flag is
   required to surface graph-only blocks such as AgentInputBlock,
   AgentDropdownInputBlock, AgentOutputBlock, OrchestratorBlock,
   and WebhookBlock and MCPToolBlock. (When running MCP tools interactively
   in CoPilot outside agent generation, use `run_mcp_tool` instead.)
5. **Find library agents for sub-agent composition**: Call `find_library_agent`
   (default mode, no `for_creation` flag) to discover reusable agents that
   can be composed as sub-agents via `AgentExecutorBlock`. This is distinct
   from the create-time similarity check in step 1 — here you're looking
   for building blocks, not asking "does the user already have this?".
6. **Generate/modify JSON**: Build or modify the agent JSON using block schemas:
   - Use block IDs from step 4 as `block_id` in nodes
   - Wire outputs to inputs using links
   - Set design-time config in `input_default`
   - Use `AgentInputBlock` for values the user provides at runtime
   - When editing, apply targeted changes and preserve unchanged parts
7. **Write to workspace**: Save the JSON to a workspace file so the user
   can review it: `write_workspace_file(filename="agent.json", content=...)`
8. **Validate**: Call `validate_agent_graph` with the agent JSON to check
   for errors
9. **Fix if needed**: Call `fix_agent_graph` to auto-fix common issues,
   or fix manually based on the error descriptions. Iterate until valid.
10. **Save**: Call `create_agent` (new) or `edit_agent` (existing) with
    the final `agent_json`.
11. **Dry-run**: ALWAYS call `run_agent` with `dry_run=True` and
    `wait_for_result=120` to verify the agent works end-to-end.
12. **Inspect & fix**: Check the dry-run output for errors. If issues are
    found, call `edit_agent` to fix and dry-run again. Repeat until the
    simulation passes or the problems are clearly unfixable.
    See "REQUIRED: Dry-Run Verification Loop" section below for details.

### Agent JSON Structure

```json
{
  "id": "<UUID v4>",        // auto-generated if omitted
  "version": 1,
  "is_active": true,
  "name": "Agent Name",
  "description": "What the agent does",
  "nodes": [
    {
      "id": "<UUID v4>",
      "block_id": "<block UUID from find_block>",
      "input_default": {
        "field_name": "design-time value"
      },
      "metadata": {
        "position": {"x": 0, "y": 0},
        "customized_name": "Optional display name"
      }
    }
  ],
  "links": [
    {
      "id": "<UUID v4>",
      "source_id": "<source node UUID>",
      "source_name": "output_field_name",
      "sink_id": "<sink node UUID>",
      "sink_name": "input_field_name",
      "is_static": false
    }
  ]
}
```

### REQUIRED: AgentInputBlock and AgentOutputBlock

Every agent MUST include one AgentOutputBlock and a way to be started: EITHER
at least one AgentInputBlock OR a webhook trigger block (never both). These
define the agent's interface — what it accepts and what it produces.

- **Regular agent**: include at least one AgentInputBlock for the values the
  user provides at runtime.
- **Triggered agent**: include a webhook trigger block instead — it is started
  by an external event, so it needs NO AgentInputBlock. Do NOT add a throwaway
  AgentInputBlock alongside a trigger; the trigger block already provides the
  agent's entry point and event payload. See "Setting Up Webhook Triggers".

**AgentInputBlock** (ID: `c0a8e994-ebf1-4a9c-a4d8-89d09c86741b`):
- Defines a user-facing input field on the agent
- Required `input_default` fields: `name` (str), `value` (default: null)
- Optional: `title`, `description`
- Output: `result` — the user-provided value at runtime
- Create one AgentInputBlock per distinct input the agent needs
- For dropdown/select inputs, use **AgentDropdownInputBlock** instead (see below)

**AgentDropdownInputBlock** (ID: `655d6fdf-a334-421c-b733-520549c07cd1`):
- Specialized input block that presents a dropdown/select to the user
- Required `input_default` fields: `name` (str)
- Optional: `options` (list of dropdown values; when omitted/empty, input behaves as free-text), `title`, `description`, `value` (default selection)
- Output: `result` — the user-selected value at runtime
- Use this instead of AgentInputBlock when the user should pick from a fixed set of options

**AgentOutputBlock** (ID: `363ae599-353e-4804-937e-b2ee3cef3da4`):
- Defines a user-facing output displayed after the agent runs
- Required `input_default` fields: `name` (str)
- The `value` input should be linked from another block's output
- Optional: `title`, `description`, `format` (Jinja2 template)
- Create one AgentOutputBlock per distinct result to show the user

Without an output block the user cannot see results, and without an input or
trigger block the agent cannot be started. NEVER skip the output block, and
always include either an input block or a trigger block.

Specialized input subclasses (`AgentDropdownInputBlock`,
`AgentGoogleDriveFileInputBlock`, `AgentShortTextInputBlock`, …) satisfy
this requirement on their own — do NOT add a throwaway base
`AgentInputBlock` alongside a specialized one. Each subclass carries its
own usage guidance (when it is required, how to configure it, how to
wire it to consumers, concrete link shape) in its block and field
descriptions; read and follow those when `find_block` surfaces a match.

### Key Rules

- **Prefer pure logic over AI**: AI blocks (text generation, orchestrators)
  cost orders of magnitude more than deterministic blocks. When a task has an
  equivalent non-AI solution — e.g. parsing, filtering, math, string formatting,
  date handling, conditionals, code execution — use the deterministic block
  (e.g. `CodeExecutionBlock`, `ConditionBlock`) instead of an LLM. Reserve AI
  for what genuinely needs it (open-ended reasoning, summarization, generation).
  Don't spend the user's money on AI for work plain logic can do.
- **Name & description**: Include `name` and `description` in the agent JSON
  when creating a new agent, or when editing and the agent's purpose changed.
  Without these the agent gets a generic default name.
- **Design-time vs runtime**: `input_default` = values known at build time.
  For user-provided values, create an `AgentInputBlock` node and link its
  output to the consuming block's input.
- **Credentials**: Do NOT require credentials upfront. Users configure
  credentials later in the platform UI after the agent is saved.
  Do NOT call `create_agent` / `edit_agent` to handle credentials, and
  do NOT redirect to the Builder. Credentials are set up inline as part
  of the run flow: `run_agent` surfaces the setup card automatically
  when credentials are missing or invalid, then proceeds to execute once
  connected. Use `connect_integration` only for a standalone provider
  setup not tied to a specific run.
- **Node spacing**: Position nodes with at least 800 X-units between them.
- **Nested properties**: Use `parentField_#_childField` notation in link
  sink_name/source_name to access nested object fields.
- **is_static links**: Set `is_static: true` when the link carries a
  design-time constant (matches a field in inputSchema with a default).
- **ConditionBlock**: Needs a `StoreValueBlock` wired to its `value2` input.
- **Prompt templates**: Use `{{variable}}` (double curly braces) for
  literal braces in prompt strings — single `{` and `}` are for
  template variables.
- **AgentExecutorBlock**: When composing sub-agents, set `graph_id` and
  `graph_version` in input_default, and wire inputs/outputs to match
  the sub-agent's schema.

### Using Sub-Agents (AgentExecutorBlock)

To compose agents using other agents as sub-agents:
1. Call `find_library_agent` to find the sub-agent — the response includes
   `graph_id`, `graph_version`, `input_schema`, and `output_schema`
2. Create an `AgentExecutorBlock` node (ID: `e189baac-8c20-45a1-94a7-55177ea42565`)
3. Set `input_default`:
   - `graph_id`: from the library agent's `graph_id`
   - `graph_version`: from the library agent's `graph_version`
   - `input_schema`: from the library agent's `input_schema` (JSON Schema)
   - `output_schema`: from the library agent's `output_schema` (JSON Schema)
   - `user_id`: leave as `""` (filled at runtime)
   - `inputs`: `{}` (populated by links at runtime)
4. Wire inputs: link to sink names matching the sub-agent's `input_schema`
   property names (e.g., if input_schema has a `"url"` property, use
   `"url"` as the sink_name)
5. Wire outputs: link from source names matching the sub-agent's
   `output_schema` property names
6. Pass `library_agent_ids` to `create_agent`/`customize_agent` with
   the library agent IDs used, so the fixer can validate schemas

### Using MCP Tools (MCPToolBlock)

> **Agent graph vs CoPilot direct execution**: This section covers embedding MCP
> tools as persistent nodes in an agent graph. When running MCP tools directly in
> CoPilot (outside agent generation), use `run_mcp_tool` instead — it handles
> server discovery and authentication interactively. Use `MCPToolBlock` here only
> when the user wants the MCP call baked into a reusable agent graph.

To use an MCP (Model Context Protocol) tool as a node in the agent:
1. The user must specify which MCP server URL and tool name they want
2. Create an `MCPToolBlock` node (ID: `a0a4b1c2-d3e4-4f56-a7b8-c9d0e1f2a3b4`)
3. Set `input_default`:
   - `server_url`: the MCP server URL (e.g. `"https://mcp.example.com/sse"`)
   - `selected_tool`: the tool name on that server
   - `tool_input_schema`: JSON Schema for the tool's inputs
   - `tool_arguments`: `{}` (populated by links or hardcoded values)
4. The block requires MCP credentials — the user configures these in the
   platform UI after the agent is saved
5. Wire inputs using the tool argument field name directly as the sink_name
   (e.g., `query`, NOT `tool_arguments_#_query`). The execution engine
   automatically collects top-level fields matching tool_input_schema into
   tool_arguments.
6. Output: `result` (the tool's return value) and `error` (error message)

### Using OrchestratorBlock (AI Orchestrator with Agent Mode)

To create an agent where AI autonomously decides which tools or sub-agents to
call in a loop until the task is complete:
1. Create a `OrchestratorBlock` node
   (ID: `3b191d9f-356f-482d-8238-ba04b6d18381`)
2. Set `input_default`:
   - `agent_mode_max_iterations`: Choose based on task complexity:
     - `1` for single-step tool calls (AI picks one tool, calls it, done)
     - `3`–`10` for multi-step tasks (AI calls tools iteratively)
     - `-1` for open-ended orchestration (AI loops until it decides it's done).
       **Use with caution** — prefer bounded iterations (3–10) unless
       genuinely needed, as unbounded loops risk runaway cost and execution.
     Do NOT use `0` (traditional mode) — it requires complex external
     conversation-history loop wiring that the agent generator does not
     produce.
   - `conversation_compaction`: `true` (recommended to avoid context overflow)
   - `retry`: Number of retries on tool-call failure (default `3`).
     Set to `0` to disable retries.
   - `multiple_tool_calls`: Whether the AI can invoke multiple tools in a
     single turn (default `false`). Enable when tools are independent and
     can run concurrently.
   - Optional: `sys_prompt` for extra LLM context about how to orchestrate
3. Wire the `prompt` input from an `AgentInputBlock` (the user's task)
4. Create downstream tool blocks — regular blocks **or** `AgentExecutorBlock`
   nodes that call sub-agents
5. Link each tool to the Orchestrator: set `source_name: "tools"` on
   the Orchestrator side and `sink_name: <input_field>` on each tool
   block's input. Create one link per input field the tool needs.
6. Wire the `finished` output to an `AgentOutputBlock` for the final result
7. Credentials (LLM API key) are configured by the user in the platform UI
   after saving — do NOT require them upfront

**Example — Orchestrator calling two sub-agents:**
- Node 1: `AgentInputBlock` (input_default: `{"name": "task"}`)
- Node 2: `OrchestratorBlock` (input_default:
  `{"agent_mode_max_iterations": 10, "conversation_compaction": true}`)
- Node 3: `AgentExecutorBlock` (sub-agent A — set `graph_id`, `graph_version`,
  `input_schema`, `output_schema` from library agent)
- Node 4: `AgentExecutorBlock` (sub-agent B — same pattern)
- Node 5: `AgentOutputBlock` (input_default: `{"name": "result"}`)
- Links:
  - Input→Orchestrator: `source_name: "result"`, `sink_name: "prompt"`
  - Orchestrator→Agent A (per input field): `source_name: "tools"`,
    `sink_name: "<agent_a_input_field>"`
  - Orchestrator→Agent B (per input field): `source_name: "tools"`,
    `sink_name: "<agent_b_input_field>"`
  - Orchestrator→Output: `source_name: "finished"`, `sink_name: "value"`

**Example — Orchestrator calling regular blocks as tools:**
- Node 1: `AgentInputBlock` (input_default: `{"name": "task"}`)
- Node 2: `OrchestratorBlock` (input_default:
  `{"agent_mode_max_iterations": 5, "conversation_compaction": true}`)
- Node 3: `GetWebpageBlock` (regular block — the AI calls it as a tool)
- Node 4: `AITextGeneratorBlock` (another regular block as a tool)
- Node 5: `AgentOutputBlock` (input_default: `{"name": "result"}`)
- Links:
  - Input→Orchestrator: `source_name: "result"`, `sink_name: "prompt"`
  - Orchestrator→GetWebpage: `source_name: "tools"`, `sink_name: "url"`
  - Orchestrator→AITextGenerator: `source_name: "tools"`, `sink_name: "prompt"`
  - Orchestrator→Output: `source_name: "finished"`, `sink_name: "value"`

Regular blocks work exactly like sub-agents as tools — wire each input
field from `source_name: "tools"` on the Orchestrator side.

### REQUIRED: Dry-Run Verification Loop (create -> dry-run -> fix)

After creating or editing an agent, you MUST dry-run it before telling the
user the agent is ready. NEVER skip this step.

#### Step-by-step workflow

1. **Create/Edit**: Call `create_agent` or `edit_agent` to save the agent.
2. **Dry-run**: Call `run_agent` with `dry_run=True`, `wait_for_result=120`,
   and realistic sample inputs that exercise every path in the agent. This
   simulates execution using an LLM for each block — no real API calls,
   credentials, or credits are consumed.
3. **Inspect output**: Examine the dry-run result for problems.
   `run_agent(dry_run=True, wait_for_result=...)` now returns the
   per-node trace directly in `execution.node_executions` on completion,
   so read it from the result and do NOT make a follow-up
   `view_agent_output` call. (Only call `view_agent_output(...,
   show_execution_details=True)` if you need the trace for a real,
   non-dry-run execution or for an execution started in a prior turn.)
   Look for:
   - **Errors / failed nodes** — a node raised an exception or returned an
     error status. Common causes: wrong `source_name`/`sink_name` in links,
     missing `input_default` values, or referencing a nonexistent block output.
   - **Null / empty outputs** — data did not flow through a link. Verify that
     `source_name` and `sink_name` match the block schemas exactly (case-
     sensitive, including nested `_#_` notation).
   - **Nodes that never executed** — the node was not reached. Likely a
     missing or broken link from an upstream node.
   - **Unexpected values** — data arrived but in the wrong type or
     structure. Check type compatibility between linked ports.
4. **Fix**: If any issues are found, call `edit_agent` with the corrected
   agent JSON, then go back to step 2.
5. **Repeat**: Continue the dry-run -> fix cycle until the simulation passes
   or the problems are clearly unfixable. If you stop making progress,
   report the remaining issues to the user and ask for guidance.

#### Good vs bad dry-run output

**Good output** (agent is ready):
- All nodes executed successfully (no errors in the execution trace)
- Data flows through every link with non-null, correctly-typed values
- The final `AgentOutputBlock` contains a meaningful result
- Status is `COMPLETED`

**Bad output** (needs fixing):
- Status is `FAILED` — check the error message for the failing node
- An output node received `null` — trace back to find the broken link
- A node received data in the wrong format (e.g. string where list expected)
- Nodes downstream of a failing node were skipped entirely

**Special block behaviour in dry-run mode:**
- **OrchestratorBlock** and **AgentExecutorBlock** execute for real so the
  orchestrator can make LLM calls and agent executors can spawn child graphs.
  Their downstream tool blocks and child-graph blocks are still simulated.
  Note: real LLM inference calls are made (consuming API quota), even though
  platform credits are not charged. Agent-mode iterations are capped at 1 in
  dry-run to keep it fast.
- **MCPToolBlock** is simulated using the selected tool's name and JSON Schema
  so the LLM can produce a realistic mock response without connecting to the
  MCP server.

### Example: Simple AI Text Processor

A minimal agent with input, processing, and output:
- Node 1: `AgentInputBlock` (ID: `c0a8e994-ebf1-4a9c-a4d8-89d09c86741b`,
  input_default: {"name": "user_text", "title": "Text to process"},
  output: "result")
- Node 2: `AITextGeneratorBlock` (input: "prompt" linked from Node 1's "result")
- Node 3: `AgentOutputBlock` (ID: `363ae599-353e-4804-937e-b2ee3cef3da4`,
  input_default: {"name": "summary", "title": "Summary"},
  input: "value" linked from Node 2's output)

### Setting Up Webhook Triggers

A **webhook trigger** runs an agent automatically when an external HTTP event
arrives. The agent must contain a webhook trigger block (surfaced by
`find_block(..., for_agent_generation=true)`); such an agent can only be
triggered, not run manually.

**To set up a webhook trigger:** call `setup_agent_webhook_trigger` with the
agent's `library_agent_id` and the trigger block's `trigger_config` (the trigger
block's configuration inputs). Read those fields from the agent's
`trigger_info.config_schema`, which `find_library_agent` returns for any
webhook-trigger agent — you do **not** need to fetch or parse the full graph
(`include_graph`) for this. Config is usually empty for a generic webhook. The
call creates a triggered preset.

**Configure the trigger through the tool, NOT by editing the graph.** The trigger
block's config (e.g. GitHub `repo` and `events`) is *not* set on the trigger node
via `create_agent`/`edit_agent` — leave those inputs unset in the graph.
`edit_agent` will reject any change to a trigger node's config fields and point
you back here. The config is stored per-trigger on the **preset** and applied to
the trigger node each time the webhook fires, so one agent can have several
triggers (e.g. different repos) each configured independently. Always supply
config via `setup_agent_webhook_trigger`'s `trigger_config` — never by editing
the node.

**Credentials must be chosen explicitly.** For provider webhooks (e.g. GitHub)
the webhook is registered under a specific account, so always ask the user which
connected account to use — never assume or auto-pick, even if only one exists.
Call `setup_agent_webhook_trigger` without `credentials` first: it returns a
setup card listing the available accounts per credential field. Ask the user
which to use, then call again with `credentials={<field_name>: <credential_id>}`.
If a field has no connected account, that **same card lets the user connect
one** — do NOT also call `connect_integration` (that just shows a second,
duplicate card).

**Trigger config must come from the user, not a guess.** If the tool returns
`trigger_config_required`, the trigger block needs configuration you must
collect from the user (e.g. a GitHub repo and which events to subscribe to). Use
the returned `config_schema` to know the fields and options, **ask the user for
the actual values — never invent them (e.g. don't make up a repository name)** —
then call again with `trigger_config` filled in.

**Two kinds of webhook:**

- **Manual-setup (generic) webhooks:** the result card shows the ingress URL with
  a copy button for the user to paste into their external service (e.g. Typeform,
  Zapier).
- **Provider webhooks (e.g. GitHub):** registered automatically once the account
  is chosen — no URL handoff is needed.

To retrieve a webhook URL later, use `list_agent_triggers` — it returns the
`webhook_url` for each webhook trigger.

**Manual UI fallback** (if the user prefers to do it themselves): Library → open
the agent → "+ New Task" → enter config → "Set up Trigger" → "Triggers" tab →
copy the webhook URL. The URL is **not** shown in the Builder.

**Managing triggers & presets after setup:** a webhook trigger is a preset — use
`list_presets` to find it, then `update_preset` (rename / pause-resume via
`is_active` / reconfigure) or `delete_preset`. Webhook triggers fire
automatically on their event and **can't** be run on demand;
`run_agent(preset_id=…)` runs only **non-webhook** presets (and `save_as_preset`
saves a run as one). See each tool's description for arguments.

### Building Trigger Agents

A **trigger agent** is a hidden, scheduled agent that watches an external
source (email, RSS, API) and runs a separate **action agent** (a.k.a.
**parent agent**) once per detected change. It's listed under the parent's
triggers, not in the user's library.

**Split into two agents (REQUIRED):** when the goal is to **poll a source and
act only when a change/event is detected**, build **TWO SEPARATE agents** — an
action agent that handles ONE event, and a trigger agent that polls and
invokes it via `AgentExecutorBlock`. Never merge polling + action into one
scheduled agent: it runs on every poll, so its run list is mostly empty polls
and the user can't tell which runs did anything. Splitting gives the action
agent **one run per real event** — the history users actually want.

**Boundary — do NOT over-split:** an agent that does the same work and emits
output every run (e.g. "email me prices every morning") stays a **single
scheduled agent**. Only split when some runs would otherwise do nothing.

**Trigger-agent pattern: Fetch → Compare → Store → Sink**

1. **Fetch**: a data-fetching block (RSS, email, HTTP request) gets the
   latest items from the source.
2. **Retrieve stored state**: `RetrieveInformationBlock`
   (ID: `d8710fc9-6e29-481e-a7d5-165eb16f8471`), scope `within_agent`, loads
   previously-seen item IDs.
3. **Compare**: a `CodeExecutionBlock` diffs fetched items against stored
   state and outputs the new ones (if any).
4. **Store updated state**: `PersistInformationBlock`
   (ID: `1d055e55-a2b9-4547-8311-907d05b0304d`), scope `within_agent`, saves
   the current state for next run.
5. **Sink** — for each new item, do one of:
   - **Run the action agent (preferred)**: `AgentExecutorBlock`
     (ID: `e189baac-8c20-45a1-94a7-55177ea42565`) runs the parent agent with
     the new item as input — one parent run per event.
   - **Start an AutoPilot session** (only when there's no reusable action
     agent): `AutoPilotBlock` (ID: `c069dc6b-c3ed-4c12-b6e5-d47361e64ce6`)
     with a prompt describing the item (e.g. "New email from {sender} about
     {subject}. Analyze and draft a reply.").

**Creating the two agents:**

1. Build and save the **action agent** first, visibly (`create_agent`); it
   takes a single event as input. Reuse an existing agent if one fits.
2. Read its `graph_id`, `graph_version`, `input_schema`, and `output_schema`
   via `find_library_agent` to wire the `AgentExecutorBlock`.
3. Build the **trigger agent** with the pattern above; set its
   `AgentExecutorBlock` `graph_id` (in `input_default`) to the action agent's
   `graph_id`, and save with `is_hidden=true`.
4. Schedule it on a cron interval (e.g. `*/15 * * * *`) via `run_agent` with
   `schedule_name` and `cron`.

The parent → trigger link is **derived from graph contents**: any hidden
agent whose graph contains an `AgentExecutorBlock` referencing the parent's
`graph_id` is listed under that parent's triggers — no explicit linking
needed.

**Managing triggers:** `list_agent_triggers` (parent's `library_agent_id`)
lists trigger agents (`kind="agent"`) and webhook presets (`kind="webhook"`);
check it before adding or deleting one. For `kind="webhook"` triggers it also
returns the `webhook_url` — give that to the user verbatim if they need to
(re)configure their external service. `list_schedules` (optionally filtered by
`graph_id`) and `delete_schedule` manage schedules — change a cron by deleting
and re-creating via `run_agent`. **Note**: editing a trigger agent makes a new
version, but the old schedule keeps running the old one — delete and re-create
the schedule after editing.
