Skip to main content
Glama

Server Details

Run LinkedIn and email outbound from Claude, ChatGPT, or any AI agent.

Ownership verified
Status
Healthy
Uptime
31.9% over 21 days
OAuth
Works in Glama
Last Tested
Transport
Streamable HTTP · MCP 2025-11-25
URL

TDQS

A3.8/5.0

Scored across 159 tools

Disambiguation4/5

Most tools target distinct resources and actions, and many descriptions explicitly route between similar-sounding tools like query_people vs query_prospects or the various discovery sources. Some overlap remains among news/company discovery tools and among canonical entity readers, but the boundaries are usually spelled out.

Naming Consistency3/5

Names are consistently snake_case, which keeps them readable, but the verb/noun ordering is mixed: most tools are verb-first, while provider-prefixed tools like apollo_enrich, exa_find_people, google_news_search, and theirstack_search use noun-first or provider-first conventions. The result is predictable enough but not a single clean pattern.

Tool Count1/5

159 tools is an extreme surface for any MCP server, far beyond the 3-15 well-scoped range. Even for a broad GTM/outreach platform, this volume risks overwhelming tool selection and making it hard for an agent to choose the right tool.

Completeness4/5

The surface covers agents, triggers, sequences, LinkedIn/email outreach, monitoring, enrichment, CRM, calendar, analytics, and settings with lifecycle tools in most areas. Minor gaps exist around direct canonical person/company creation or deletion and attachment upload/delete, but core workflows are well represented.

Available Tools

159 tools
add_triggerAdd TriggerAInspect

You MUST call get_skill_guide('trigger_code') before writing the trigger's code — the qualification rules, sandbox globals, tool surface, and out contract live in the skill. New code runs on the trigger's next firing.

The same goal-sync obligation update_trigger carries applies to creating one: a new scheduled trigger adds recurring behavior the goal doesn't yet describe, and the goal is injected into every turn on this agent. After this call, name what the new trigger does and how often via update_agent(agent_id=..., goal=...) in the same turn. A flow's per-node on_enter triggers are already covered by the goal's sequence description and need no separate mention.

To add a flow node's on_enter enactment — a terminal hook, or re-arming a node whose row was removed — pass an on_enter trigger with the target node_id (any node kind; the node must already exist in the sequence). One on_enter per node — if it already has one, edit that with update_trigger instead of adding a second. On a send or connection-request node, that trigger's prompt holds only extra instructions; the message or note itself goes in the node's message_templates (update_node). Dict with success, agent_id, trigger_id, next_run_at, and the added trigger's full dict under trigger, plus a warning when the agent is paused, since its triggers don't fire until it is set back to active. Read the agent's other triggers via get_agent_details.

ParametersJSON Schema
NameRequiredDescriptionDefault
triggerYesThe trigger to add. In its prompt, name any outside resource a run reads (a Google Sheet, doc, file, URL) by the identifier its tool takes (the spreadsheet ID and tab, the URL), not only by its title — a later run doesn't see this chat.
agent_idYesID of the agent to add the trigger to

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only say readOnlyHint=false/destructiveHint=false. The description adds substantial behavior beyond that: new code executes on the trigger's next firing, a goal-sync obligation via update_agent must happen in the same turn, one on_enter per node limit, the paused-agent warning, and the credential/context implication that a later run doesn't see this chat.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the purpose and the sibling preference, then layered detail. It is long, but for a tool with six trigger variants and cross-tool obligations every sentence is load-bearing; only mild compression is possible.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

A complex, multi-variant mutation tool with no output schema but an inline <returns> block describing success, agent_id, trigger_id, next_run_at, the trigger dict, and the paused warning. Combined with the annotations and detailed usage rules, an agent has everything needed to invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description meaningfully extends it: the on_enter trigger's prompt holds only extra instructions while the message itself goes in message_templates, and prompts should name outside resources by the identifier the tool takes rather than by title. This is real semantic guidance beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Add a trigger to an existing agent') and immediately scopes it against the sibling it is not ('Prefer this over creating a new agent'). An agent can distinguish it from create_agent, update_trigger, and define_sequence without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use (schedule follow-ups on an agent with context), when-not (prefer this over creating a new agent), the alternative for editing (use update_trigger if a node already has an on_enter), and a mandatory prerequisite (get_skill_guide('trigger_code') before writing code). Routing is unambiguous.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

apollo_enrichApollo EnrichA
Read-only
Inspect

Pass all candidate domains at once — one call with the full list, not one call per domain. The tool fans them out to Apollo's /organizations/enrich endpoint in parallel (ThreadPoolExecutor, max 20 concurrent, well within Apollo's 1000 req/min limit), so a batch costs one tool round instead of N.

Org-only (company domains, not people). For person enrichment (work email, LinkedIn URL), use find_email instead.

Costs 1 Sliq credit per found=True result (free for found=False, free on BYO Apollo). Soft-fails on insufficient credits — already- enriched results are still returned.

Per-user 24h cache: any domain this user already enriched — in an earlier run_code sandbox, an earlier call, or an earlier run — is served from cache with no second Apollo call and no second charge (both found=True and found=False are cached). The input list is also deduped by canonical form before fan-out.

Each enriched company is also saved to your canonical company entities, queryable via query_companies. List of dicts, one per input domain, in the same order. Each is either: {found: True, name, domain, industry, employee_count, annual_revenue, founded_year, latest_funding_stage, total_funding, location, linkedin_url, description} OR: {found: False, queried_domain, message}

latest_funding_stage is Apollo's title-case display string (e.g. "Seed", "Series A", "Series B", ..., "Series Unknown", "Private Equity", "Post-IPO Equity", "Other") — passed through verbatim, NOT a lowercase_underscore enum. It is None/absent when Apollo has no funding data for the org. total_funding is a USD integer (Apollo defaults it to 0, not null, when absent). Apollo's funding coverage is patchy, especially for bootstrapped / PE-owned / privately-held companies. Callers gating on these fields should decide how to handle missing data — matches_icp includes-on-null rather than excludes, so legit rows with thin Apollo coverage aren't silently dropped, and normalizes case + separators when comparing the stage string.

ParametersJSON Schema
NameRequiredDescriptionDefault
domainsYesList of company domains (e.g. ["acme.com", "foo.io"]). Order is preserved in the return list. Duplicates (after canonicalization) are collapsed and the result is shared.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=true. The description goes far beyond that: it discloses the parallel fan-out mechanism (ThreadPoolExecutor, max 20 concurrent), the cost model (1 Sliq credit per found=True, free for found=False), soft-failure behavior on insufficient credits, the per-user 24h cache, dedup behavior, and that results are saved to canonical company entities. No contradiction with readOnlyHint—enrichment is a lookup/read operation that stores results for the user's workspace but does not modify external state in a way that violates a read-only hint. This is rich behavioral transparency that annotations alone would never provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core usage rule in the very first sentence, then layers details in a logical order: batching, endpoint, cost, caching, dedup, persistence. Every paragraph earns its place. It is long, but the length is justified by the behavioral and cost details an agent needs to avoid misuse. A 4 rather than 5 because the <returns> block is quite verbose and could be trimmed without losing essential guidance—some of the funding-stage formatting detail could live in the schema or output schema instead.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity—costing, caching, dedup, batched parallel execution, and two distinct output shapes—the description covers everything an agent needs to call it correctly and interpret results. It even explains how missing funding data affects downstream matching (matches_icp includes-on-null). There is no output schema, so the description properly carries the burden of explaining the return structure. The only minor gap is a lack of an explicit 'when not to use' list beyond person enrichment, but it names the key sibling and the coverage is otherwise complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%—the single 'domains' parameter is fully described in the schema, including order preservation and duplicate collapsing. The description reinforces the batch usage ('Pass all candidate domains at once') and adds the canonicalization detail, but it doesn't need to add much beyond what the schema already states. Baseline 3 is appropriate for full schema coverage with minor descriptive reinforcement.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Enrich multiple company domains in a single call.' It clearly distinguishes this tool from siblings like find_email and enrich_linkedin_profiles by specifying org-only enrichment (company domains, not people). The phrase 'Enrich multiple company domains in a single call' is specific and action-oriented, leaving no ambiguity about what the tool does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly tells the agent when to use this tool: pass all candidate domains at once, one call with the full list, not one call per domain. It also names an alternative—'For person enrichment (work email, LinkedIn URL), use find_email instead'—and identifies the sibling tool by name. This gives the agent both a positive usage rule and an explicit exclusion, which is exactly what the dimension asks for.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

apollo_saveApollo SaveA
Destructive
Inspect

Save people as contacts or companies as accounts in the user's Apollo CRM. These will be visible in Apollo's web UI. ALWAYS confirm with the user before calling this tool.

IMPORTANT: Each contact MUST have an email address — it is the only required field. Do NOT pass masked/obfuscated names (e.g. "Tr***k") — use apollo_enrich first to get full details. Do NOT pass only apollo_id without email — Apollo's contacts API ignores apollo_id and will create blank contacts. Always enrich contacts first, then save with {first_name, last_name, email, title, organization_name}.

For accounts, the result splits the count into "created" (new rows) and "existing" (already in the CRM — Apollo dedupes by domain); "saved" is their sum, so a re-save reads as saved == requested rather than a false failure.

ParametersJSON Schema
NameRequiredDescriptionDefault
typeYes"contacts" or "accounts"
itemsYesList of dicts. For contacts: {first_name, last_name, email (REQUIRED), title, organization_name}. For accounts: {name, domain}.
sequence_idNoOptional. If provided, contacts will also be added to this Apollo sequence for outreach.

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the destructiveHint annotation, the description discloses important behaviors: writes are visible in the Apollo UI, apollo_id is ignored and can create blank contacts, accounts are deduped by domain, and the result counts are split into created/existing/saved. This substantially protects the agent from misinterpreting outcomes.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core action, then confirmation, required fields, and important caveats. It is somewhat dense but every sentence conveys operational value, and the account result explanation is placed at the end where it provides useful context without burying the main usage rules.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutating tool with no output schema, this description covers required inputs, common failure modes, deduplication behavior, and result semantics for accounts. The main gap is that contact-level result behavior is not described, but overall the agent has enough context to call the tool correctly and interpret responses.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already covers all three parameters, but the description adds critical meaning: email is required for contacts, contacts should be passed as {first_name, last_name, email, title, organization_name}, accounts use {name, domain}, and sequence_id adds contacts to a sequence. This goes beyond the bare schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Save') and resource ('people as contacts or companies as accounts in the user's Apollo CRM'), clearly distinguishing this write operation from enrichment/search siblings. It also specifies the two supported object types, leaving no ambiguity about what the tool does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly requires user confirmation before calling and instructs the agent to use apollo_enrich first to get full details, which is actionable workflow guidance. It does not fully enumerate when not to use this tool versus similar CRM update tools, but the preconditions and prohibitions are clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

append_learningAppend LearningAInspect

BEFORE COMPOSING YOUR BATCH, filter each candidate entry. A learning is ONLY:

  • A reusable rule about how the agent should run, that applies to every future firing, not just this one.

A learning is NOT (these are the most common mistakes — produce zero of these):

  1. Per-entity / per-contact facts. Anything naming a specific company, person, deal, or URL. Examples that DO NOT belong here: "Heitman LLC (heitman.com) is a real estate firm in Chicago", "Jamestown Management hired a new CFO", "Alice Chen's first name is 'Alice'", "ExampleCo: 200 waitlist, 30 live users". → Use track_prospects(agent_id, items=[...]) for per-entity lifecycle tracking, OR → update_workspace(section='outputs', key='<your_list>', value=[...]) to store as an agent deliverable.

  2. Per-run summaries. Anything describing what happened on a specific date or run. Examples: "Follow-up run 2026-04-27: 0 people in 'messaged' stage", "Apr 23 scan: same source returned, no new angles". These are auto-saved to the agent's run history every run — don't duplicate them here.

  3. General user preferences. "User wants concise emails", "user prefers Tuesday meetings". → Use save_memory.

Valid learnings look like rules-of-thumb, not observations. They're short prose, no proper nouns, no dates, no per-entity data. Good examples:

  • "PostHog returns 1-day data, not an error — if the query returns one row it's complete, not partial."

  • "Internal team syncs rarely produce postable ideas — skip quickly to save Tavily budget."

  • "r/SaaS posts about LinkedIn automation pain are strong leads."

  • "Tavily score < 0.09 against generic signal_keyword queries is reliably tangential — keep min_score=0.09."

If you cannot rewrite your candidate entry into a rule-of-thumb shape without naming a specific entity, date, or run number, it doesn't belong here — store it via track_prospects or update_workspace instead.

USAGE

Pass learnings=["...", "..."] to add one or more new entries in one call. The whole batch is atomic — either all entries are appended or none are (see "Cap behavior" below). Singleton case is learnings=["..."]. Batching is strictly preferred over multiple single-entry calls: one round-trip per run is dramatically cheaper in latency and tokens than one round-trip per insight.

Pass replace_with=[...] to atomically replace the full list — use this when the prior call returned a "cap reached" ModelRetry asking you to consolidate.

Scope: per-agent and persistent. Distinct from save_memory (per-user preferences).

CAP BEHAVIOR

The list is capped at 25 items. If a batch would push the list over the cap (current_count + batch_size > 25), the WHOLE batch is rejected via ModelRetry asking you to consolidate first via replace_with. After consolidating, re-submit your full batch — no need to track which entries "landed" since none were stored on the rejected call. Consolidate by DROPPING entries that match the anti-examples above (per-entity facts, per-run summaries), not by reshuffling — the cap is a forcing function for hygiene, not a length limit on the same content. Dict with success, agent_id, and the new learnings count.

ParametersJSON Schema
NameRequiredDescriptionDefault
agent_idYesID of the agent
learningsNoNew learning entries to append in one batch (mutually exclusive with replace_with)
replace_withNoFull replacement list for consolidation (mutually exclusive with learnings)

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond annotations (readOnlyHint=false, destructiveHint=false), the description discloses significant behavior: the list is capped at 25, over-cap batches are rejected atomically via ModelRetry, replace_with atomically replaces the full list, and both learnings and replace_with batches are all-or-nothing. It also explains persistence scope (per-agent) and the return value shape. Nothing in the description contradicts the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but highly structured with clear sections: a summary, explicit anti-examples, valid examples, usage, and cap behavior. The core action is front-loaded in the first sentence, and every subsequent section earns its place by preventing the exact mistakes an agent would otherwise make. The formatting makes the density navigable rather than bloated.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, cap semantics, and lack of an output schema, the description is complete: it covers valid entries, invalid entries, routing alternatives, batching atomicity, cap-rejection behavior, consolidation guidance, and return value summary. An agent has everything needed to decide whether to call it and how to structure the arguments successfully.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although schema coverage is 100%, the description adds essential semantics beyond the raw parameter names: learnings is for appending a batch, replace_with is the full-replacement path after cap consolidation, and the two are mutually exclusive. It also defines what constitutes a valid learning string (rule-of-thumb, no proper nouns/dates/per-entity data), which is crucial for correct invocation and not conveyed by the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: append agent-specific operational RULES to the agent's persistent learning list for the next run, or replace the entire list for consolidation. It sharply differentiates from sibling tools like track_prospects, update_workspace, and save_memory by explicit scoping: per-entity facts, per-run summaries, and general user preferences are excluded. The inclusion of concrete good and bad examples removes ambiguity about what the tool is for.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly states when to append vs. replace, when to use track_prospects/update_workspace/save_memory instead, and how to handle a cap-reached ModelRetry. It also gives precise batching guidance: pass learnings=[...] for one or more entries, and use replace_with after consolidation. No inference is required to decide when this tool is appropriate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

attach_to_outreachAttach To OutreachAInspect

Attach a file from the user's library to a queued outreach email.

Only attach when the user explicitly asks to attach a specific file to a recipient's email — never on your own initiative. recipient_email targets a queued email (pending or awaiting approval); attachment_id comes from list_attachments. The same file can be attached to several emails.

ParametersJSON Schema
NameRequiredDescriptionDefault
attachment_idYesa file id from list_attachments.
recipient_emailYesthe queued email's recipient.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only indicate non-read-only and non-destructive, so the description carries the burden of explaining behavior. It adds useful context: the email must be queued, attachment IDs come from list_attachments, and the same file can be attached to multiple emails. It does not contradict annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact, front-loaded with the core action, and every sentence adds value. The consent guardrail and parameter sourcing notes are included without unnecessary filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a two-parameter tool with complete schema coverage, the description provides enough context to invoke it correctly: when to attach, what target emails qualify, and where the attachment ID comes from. It does not describe error behavior or the result of attaching to a non-queued email, but those gaps are minor.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents both parameters. The description slightly enriches recipient_email by clarifying the email must be pending or awaiting approval and attachment_id by tying it to list_attachments, but most parameter meaning is already in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action — attaching a file from the user's library to a queued outreach email — and clearly distinguishes this from sibling tools like detach_from_outreach or get_email_attachment. The verb, resource, and target are all explicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives strong usage guidance: only attach when the user explicitly asks, never on the tool's own initiative, and only to queued emails (pending or awaiting approval). It does not explicitly name an alternative tool for the opposite operation, such as detach_from_outreach, so it falls just short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

check_email_daily_usageCheck Email Daily UsageA
Read-only
Inspect

Call this before queuing bulk email outreach — the mailboxes list tells you which mailboxes still have headroom today. Each connected mailbox has its own daily cap (Gmail/Outlook score reputation per from address, not per Sliq user). An empty mailboxes list means no email account is connected — tell the user to connect a Gmail or Outlook mailbox in Settings. { 'emails': { 'mailboxes': [ {'email': str, 'provider': str, 'sent_today': int, 'daily_limit': int, 'remaining_today': int, 'is_default': bool}, ... ], }, 'queue': { 'pending': int, 'pending_approval': int, 'sent_total': int, 'failed': int, 'next_send': str, # optional }, }

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, and the description adds meaningful context: daily caps are per mailbox/from-address rather than per user, and an empty mailboxes list means no account is connected. This goes beyond the annotation and clarifies interpretation of results.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary is front-loaded with purpose and timing, followed by two concise operational notes. The returns block is structured and compact. Every sentence adds decision-relevant value without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description fully documents the return structure, including queue fields and optional next_send. It also explains how to interpret empty results and what action to take. Combined with a read-only annotation, this is complete for safe, correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters and no input schema burden, so there is nothing for the description to add. The description instead clarifies the output's mailboxes field, which is the relevant semantic content for this tool.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description opens with a specific verb and resource: 'Check per-mailbox email outreach usage limits and queue status.' It clearly identifies email-specific mailboxes and queue, distinguishing it from check_linkedin_daily_usage and other usage tools without ambiguity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to call: 'Call this before queuing bulk email outreach.' It also gives actionable guidance for an empty mailboxes list. It does not explicitly name alternative tools or when not to use it, but the context is clear enough.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

check_linkedin_daily_usageCheck Linkedin Daily UsageA
Read-only
Inspect

Call this before sending multiple LinkedIn messages, InMails, or connection requests to get a fresh read of today's headroom (limits, remaining, ramp-up) plus the current queue counts — re-call it after queueing or approving sends, which leave the in-context figures out of date. For the per-contact queue (names, scheduled times, messages), use manage_linkedin_invite_queue with action='status'. Dict with usage counts, limits, queue counts, and configurable_limits — per action the standard default, the tier-aware ceiling (the max the user can raise it to), and the user's current custom cap (None = using the default). Quote ceiling when the user asks how high they can set a cap.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description discloses that in-context figures go stale after queueing or approving sends, making a re-call necessary. It also explains the return value's configurable_limits semantics (default, ceiling, custom cap, and that None means using the default), adding meaningful behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core purpose, followed by concrete when-to-use guidance and a structured returns section. Each sentence earns its place; there is no fluff or repetition of schema/annotation data.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter, read-only tool with no output schema, the description is complete: it states what the tool does, when to call it, when to call it again, what the return contains, and which sibling covers per-contact queue details. Nothing essential is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters and the schema description coverage is 100%, so there are no parameter semantics to explain. The baseline for zero-parameter tools is 4; the description adds no unnecessary parameter information.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Check LinkedIn usage limits and invite queue counts.' It clearly distinguishes itself from the sibling check_email_daily_usage (email usage) and explicitly scopes out manage_linkedin_invite_queue's per-contact queue status, so an agent can tell which tool is which.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage guidance is explicit: call before sending multiple LinkedIn messages/InMails/connection requests to get a fresh read, and re-call after queueing or approving sends because prior figures become out of date. It also names the exact alternative (manage_linkedin_invite_queue with action='status') for per-contact queue details.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

classify_message_tag_groupClassify Message Tag GroupAInspect

Classification costs credits (a flat per-message rate). The return carries the count of messages the run will process and the credits it will charge (exact — the same prospect-resolved population the run bills), so you can tell the user the cost; a run never charges more than the balance. If the balance can't cover the estimate the run is not enqueued — the return says so, and you should relay it. Dict with the in-scope message count and estimated credit cost, plus whether the run was enqueued or blocked for insufficient credits. Enqueued -> {"enqueued": true, "group_id", "message_count", "estimated_credits"}. Blocked -> {"enqueued": false, "reason": "insufficient_credits", "group_id", "message_count", "estimated_credits", "balance"}.

ParametersJSON Schema
NameRequiredDescriptionDefault
forceNoRe-classify messages already tagged for this group.
channelNo'linkedin' or 'email' to bound the population; omit for both.
group_idYesThe tag group to classify (from list_message_tag_groups).
agent_idsNoScope classification to specific campaigns (agent_tasks ids from a group's `classifiable_campaigns`); omit for every campaign. `[]` classifies zero.
sent_afterNoISO-8601 timestamp; only messages sent on or after it are classified. Omit to classify all sent messages.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only say readOnlyHint=false and destructiveHint=false, which is sparse. The description goes far beyond by disclosing that classification runs in the background, results are not immediate, credits are charged at a flat per-message rate, the return provides exact count and cost, and the run is blocked if balance is insufficient. It also clarifies that a run never charges more than balance and that blocked runs are reported in the return. This is comprehensive behavioral transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with a <summary> and <returns> section, front-loading the purpose and then detailing behavior, cost, and return format. It is a bit lengthy but every sentence carries information; no redundancy. The structure makes it scannable and easy to parse for an agent.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has no output schema, but the description's <returns> section explicitly details both enqueued and blocked return dicts with exact keys. It covers cost implications, async behavior, filtering options, and points to result-reading tools. It also handles the edge case of insufficient credits. For a complex background operation, this is exceptionally complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so baseline is 3. The description adds semantic context for force (re-tag after editing), channel (narrows to LinkedIn/email), and sent_after (only messages on/after instant). It reinforces the default behavior and clarifies the effect of each filter beyond the schema's literal definitions. However, it does not mention agent_ids at all, leaving that to the schema (which is adequate). Overall, it adds meaningful value.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a clear purpose: "Kick off classification of the group's in-scope sent messages, in the background." It specifies the resource (sent messages for a group), the action (classify), and the async nature. It also distinguishes itself from sibling tools such as estimate_message_tag_classify_scope (which estimates scope) and get_message_tag_rates (which reads results), and its scope definition via criteria (channel, campaigns, positions, teammates) makes it unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description states when to use the default (incremental catch-up of unclassified messages) and when to use force=True (re-tag after editing group/tags). It also explains filters (channel, sent_after) and how to retrieve results afterward with get_message_tag_rates / query_tagged_messages. However, it does not explicitly mention the alternative cost-estimation sibling (estimate_message_tag_classify_scope) as a pre-check, though usage is strongly implied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

classify_segment_groupClassify Segment GroupAInspect

Classification costs credits (a flat per-person rate). The return carries the count of people the run will process and the credits it will charge (exact — the same population the run bills), so you can tell the user the cost; a run never charges more than the balance. If the balance can't cover the estimate the run is not enqueued — the return says so, and you should relay it. Dict with the in-scope person count and estimated credit cost, plus whether the run was enqueued or blocked for insufficient credits. Enqueued -> {"enqueued": true, "group_id", "person_count", "estimated_credits"}. Blocked -> {"enqueued": false, "reason": "insufficient_credits", "group_id", "person_count", "estimated_credits", "balance"}.

ParametersJSON Schema
NameRequiredDescriptionDefault
forceNoRe-classify people already tagged for this group.
group_idYesThe segment group to classify (from list_segment_groups).

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=false and destructiveHint=false; the description adds genuinely new behavioral context: it is asynchronous (~15 min tagging delay), costs credits at a flat per-person rate, never charges more than the balance, and is simply not enqueued when the balance is insufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded summary sentence followed by usage, cost, and return semantics, each earning its place. No redundant restatement of the name or annotations.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Even with no output schema, the description documents the return shape for both enqueued and blocked outcomes, the credit/balance semantics, and the async nature. An agent has everything needed to call it and interpret the result.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds meaning over the schema: without force only not-yet-classified people are processed, while force triggers a full re-tag after editing the group or its tags. This clarifies the practical effect of the flag.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb+resource: 'Kick off classification of the group's in-scope canonical people, in the background.' It also defines scope (campaigns and teammates) and distinguishes itself from the sibling classify_message_tag_group and the read-side get_segment_rates / query_segment_people.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit when-to-use: 'call this right after creating a group to tag the people already there now, or with force=True to re-classify everyone.' It also says results are not immediate and directs the agent to get_segment_rates / query_segment_people afterward, and to relay blocked runs.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

correct_message_classificationCorrect Message ClassificationAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
sourceYesWhich table the message is in — 'linkedin_data' (a LinkedIn DM), 'email_data' (a sent email), or 'linkedin_queue' (a connection-request note). This is a message's `source` field from list_sent_messages or query_tagged_messages.
tag_idsYesThe corrected tag ids (from the group's tags). At most one for a single-tag comparison; an empty list clears the message to "No match".
group_idYesThe tag group the message is classified under (from list_message_tag_groups).
source_idYesThe message's row id — a message's `source_id` from query_tagged_messages.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only say readOnlyHint=false and destructiveHint=false, but the description adds substantial behavior: the correction REPLACES existing tags, recomputes group rates and message views immediately without re-classification, and persists past later re-tags so newly-added tags are not applied. These are non-obvious traits an agent must know.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The prose is dense and front-loaded, leading with the core purpose before prerequisites and edge cases. It runs long, but nearly every sentence (persistence, recompute, empty-list clearing) carries actionable meaning, so little is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 4-required-param mutation tool with no output schema, the description covers identifiers, tag semantics, side effects, persistence, and even an inline returns block describing the confirmation dict — leaving nothing an agent needs missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3, but the description adds genuine semantics beyond the schema: a single-tag comparison accepts at most one tag id and an empty list clears to 'No match'. It also reinforces that tag ids must come from the group's tags, tying parameters to the workflow.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource — setting or correcting tags on one sent message within a tag group — and immediately scopes it as the 'human-set override' for a single (group, message), distinguishing it from sibling bulk classifiers like classify_message_tag_group.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit trigger conditions ('when the classifier got it wrong or skipped it') and names the sibling tools to source the required identifiers from (list_sent_messages, query_tagged_messages, list_message_tag_groups). It also states behavior for the edge case of clearing to 'No match'.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

correct_reply_outcomeCorrect Reply OutcomeAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
channelYes'linkedin' or 'email' — which reply table the message_id is in.
outcomeYesThe corrected outcome — 'interested', 'meeting_booked', 'not_interested' (an explicit decline), or '' for neither (a plain reply).
message_idYesThe inbound reply's row id (email_data / linkedin_data PK), from a message-search result. Must be a reply the user received.

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations (write-capable, non-destructive), the description discloses the tool's directional stage effects, explains the not_interested stage semantics and its funnel treatment, and explicitly notes it can lower or raise stages. This is substantive behavioral context that annotations alone do not provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but densely informative, with a clear front-loaded summary and structured sections. Every sentence adds decision-relevant detail, and the return format is neatly separated. No filler or repetition of schema content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity, the fully documented schema, and the rich description, an agent has everything needed to select and invoke it correctly. It covers the alternative tool, parameter semantics, message_id sourcing, and return value shape despite no output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents the three parameters well. The description adds non-obvious semantic value by explaining what outcome='not_interested' does to the prospect's stage and how it is counted in the funnel, plus how to obtain message_id from message-search tools.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb+resource: re-judge one inbound reply's outcome and re-derive the prospect's stage. It clearly differentiates the tool from sibling update_prospect by noting that this tool moves the stage in either direction while update_prospect only raises. The scope is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit when-to-use and when-not-to-use guidance: use it to correct false positives/under-called replies, and reach for update_prospect when marking a rung with no reply behind it. It also names the lookup tools for finding message_id, leaving the agent with a clear decision path.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

correct_segment_classificationCorrect Segment ClassificationAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
tag_idsYesThe corrected tag ids (from the group's tags). At most one for a single-tag group; an empty list clears the person to "No match".
group_idYesThe segment group the person is classified under (from list_segment_groups).
person_idYesThe canonical person to correct (from query_segment_people / query_people).

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With readOnlyHint=false and destructiveHint=false, the description still clearly discloses that this replaces a person's tags, that an empty list clears to 'No match', that rates/people views recompute immediately, and that the override persists across re-tags. This is rich behavioral context beyond what the annotations provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary is front-loaded with the purpose, then follows with identity/the ID sources, parameter constraints, side effects, and persistence. Every sentence adds operational value, and the returns section is compact and useful.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a persistent, state-changing override with no output schema, the description is thorough: it explains the return value, the empty-list clearing behavior, immediate recomputation, and the fact that a later re-tag will not overwrite the correction. Nothing needed to call it correctly appears to be missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already has 100% description coverageasiatic. The description largely restates the tag_ids semantics and the sources for group_id/person_id, adding only minor workflow guidance about identifying the person first and passing corrected tag ids from the group's tags.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: it corrects/replaces the tags on one person for a segment group when the classifier is wrong. It distinguishes itself from message-classification and reply-outcome correction siblings by scoping to 'one person' and 'segment group' tags.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives clear usage context: use this as the human-set override when the classifier got a person's tags wrongging. It also names the source queries for person_id and explains constraints for single-tag groups states an explicit when-condition but does not name alternatives like classify_segment_group or correct_message_classification.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_agentCreate AgentAInspect

Create a background agent that runs automatically based on triggers.

Do NOT create a new agent for follow-ups, event handlers, or scheduled checks that continue an existing agent's work — use add_trigger on that agent instead.

When the agent matches one of the built-in agent templates — the template_id enum lists them — set its template_id and seed workspace.inputs per that agent's skill guide. The template_id runs the template's setup; seed a template's inputs without it and the agent is not fully wired up. If no template fits, omit template_id and the agent runs as a general background agent.

You MUST call get_skill_guide('trigger_code') before writing any trigger's code — the qualification rules, sandbox globals, tool surface, and out contract live in the skill. code runs on the trigger's next firing.

ParametersJSON Schema
NameRequiredDescriptionDefault
goalYesComplete instructions for what the agent should accomplish. Write as if instructing another assistant. Name any outside resource a run reads (a Google Sheet, doc, file, URL) by the identifier its tool takes (the spreadsheet ID and tab, the URL), not only by its title — a later run doesn't see this chat.
titleYesShort name for the agent (e.g. "Outreach to Q2 leads", "Email triage")
triggersYesList of triggers that determine when the agent runs. An agent can have multiple triggers of different types (e.g. one schedule plus one event handler) — they fire independently. At least one trigger is required. In each trigger's prompt, name any outside resource a run reads (a Google Sheet, doc, file, URL) by the identifier its tool takes (the spreadsheet ID and tab, the URL), not only by its title — a later run doesn't see this chat.
workspaceNoOptional `{'inputs': {...}, 'outputs': {...}}` dict that seeds the agent's workspace. Name any outside resource a run reads (a Google Sheet, doc, file, URL) by the identifier its tool takes (the spreadsheet ID and tab, the URL), not only by its title — a later run doesn't see this chat.
template_idNoOptional template ID.

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations provide almost no coverage (readOnlyHint=false, destructiveHint=false are defaults), so the description carries the full burden. It discloses important behavioral traits: code runs on the trigger's next firing, template_id runs template setup and incomplete seeding leaves the agent unwired, and get_skill_guide('trigger_code') is a mandatory prerequisite. This is substantial behavioral context beyond the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is structured into short, purposeful paragraphs, each carrying a distinct decision or rule: purpose, exclusion, template wiring, and the code prerequisite. The most important scoping statement is front-loaded, and every sentence earns its place; nothing is redundant with the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity — multiple trigger types, template wiring, and the need to reference an external skill guide — the description covers the critical decisions needed to call the tool correctly. The input schema provides the detailed trigger shapes, and the description supplies the inter-field relationships and mandatory prerequisites that the schema cannot express. No return-value explanation is needed for a creation tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description adds meaning beyond the schema by explaining template_id behavior, the dependency between template_id and workspace.inputs, and the required get_skill_guide call before writing trigger code. It doesn't deeply expand every parameter, but it meaningfully supplements the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: 'Create a background agent that runs automatically based on triggers.' The opening sentence conveys exactly what the tool produces, and the second paragraph explicitly distinguishes it from add_trigger, so an agent can tell it apart from the most relevant sibling without ambiguity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-not-to-use guidance: 'Do NOT create a new agent for follow-ups, event handlers, or scheduled checks that continue an existing agent's work — use add_trigger on that agent instead.' It also provides template-selection rules, when to omit template_id, and when workspace.inputs must be seeded. This is direct, actionable guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_calendar_eventCreate Calendar EventA
Destructive
Inspect

Use search_emails or search_calendar to find email addresses for prior attendees. For new attendees whose email you don't have, use find_email. Dict with success status and event details

ParametersJSON Schema
NameRequiredDescriptionDefault
bodyNoOptional event description/notes (plain text)
subjectYesEvent title
locationNoOptional event location
providerNoWhich calendar to use ('outlook' or 'google'). Only needed if the user has both connected -- ask them which to use, then pass it here.
attendeesNoOptional list of attendees. Each is an object with required "email" and optional "name". Examples: [{"email": "alice@acme.com", "name": "Alice Smith"}] or [{"email": "alice@acme.com"}].
in_personNoALMOST ALWAYS OMIT THIS PARAMETER or set to false. ONLY set to true if the user literally says "in-person", "in person", "at my office", or "no video link". If there is ANY ambiguity, do NOT set this to true. A video meeting link is automatically added when this is false (the default).
end_datetimeYesEnd time as ISO 8601 in the user's LOCAL timezone, not UTC (e.g., "2026-02-17T11:00:00"). Do not include a timezone offset or Z suffix. Default to 30 minutes after start if user does not specify.
start_datetimeYesStart time as ISO 8601 in the user's LOCAL timezone, not UTC (e.g., "2026-02-17T10:00:00"). Do not include a timezone offset or Z suffix.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations (readOnlyHint=false, destructiveHint=true) already establish this is a mutating write, and the description does not contradict them. It adds genuinely useful behavioral context beyond the annotations: a video link is auto-included unless in_person is true, and user confirmation is mandatory before invocation. No contradiction detected.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact, front-loaded with the core purpose, and structured with <summary> and <returns> tags. Every sentence earns its place — the confirmation requirement and attendee-lookup routing are operationally necessary, not filler. Slight deduction for the returns clause being thin, though that is more a completeness than a conciseness issue.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Operational context is strong: confirmation protocol, provider ambiguity handling, and attendee lookup are covered. However, there is no output schema, and the returns section only says 'Dict with success status and event details' without specifying keys, error cases, or the shape of the created event — a notable gap for an 8-parameter creation tool with no structured return definition.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all 8 parameters in detail, including unusually rich guidance on in_person and provider. The description's prose adds only the in_person/video-link linkage, which is a modest supplement; the baseline of 3 is correct since the schema carries the parameter-semantics burden.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Create a calendar event') with clear scope ('user's connected calendar (Outlook or Google)'). The video-meeting-link behavior further disambiguates it from siblings like update_calendar_event, create_task, and create_todo, so an agent can select it correctly without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit gate for when to call the tool ('ALWAYS present the event details to the user and ask for explicit confirmation before calling') and explicit alternative routing for attendee discovery ('Use search_emails or search_calendar... use find_email'). This is actionable, conditional guidance rather than implied usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_crm_taskCreate Crm TaskAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
notesNoFree-text notes.
titleYesWhat to do, e.g. "Send pricing to Jane".
deal_idNoLink a deal (query_deals `id`).
due_dateNoThe day it's due, YYYY-MM-DD. Use resolve_date first for words like "next Friday". Omit for no due date.
person_idNoLink a person (query_people `id`).
company_idNoLink a company (query_companies `id`).
assignee_emailNoWho should do it, an active teammate's email from list_teammates (not an invited one). Default: the user.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only indicate it is a non-read, non-destructive operation mirroring a create. The description adds meaningful behavior beyond annotations: the task is visible on the team-wide Tasks view, can stand alone or link to a person/company/deal, and will not send a due-date notification.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well-structured with a summary and return section, front-loading the core action and purpose. Every sentence adds value-the examples, link behavior, team visibility, and no-alarm caveat are all useful and no redundant filler exists.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 7-parameter create tool with no output schema, the description provides the essential return shape ('same shape as query_crm_tasks row'), clarifies visibility, linking options, and due-date behavior. The schema covers parameter syntax, so nothing needed for a correct call is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already covers 100% of parameters with helpful descriptions, so the description does not need to re-explain semantics. It adds context about linking to people/companies/deals and the return shape, but these are minor complements to a fully-documented schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it creates a task (a to-do) in the user's CRM-has a specific verb, resource, and scope. It distinguishes itself from siblings like create_calendar_event and update_crm_task by emphasizing CRM task semantics and linking to deals/people/companies.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit use cases ('remind me to send pricing to Jane on Friday') and explicitly warns that this is not an alarm, telling the agent to communicate that limitation. It does not name alternative tools explicitly, but the context and examples make when-to-use reasonably clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_dealCreate DealAInspect

A deal needs a person or a company (or both). Link someone Sliq already knows by id (query_people / query_companies), or pass new_person / new_company to add them. If the person already has an open deal this refuses and names it — update that deal instead, unless the user wants a second one (then pass allow_duplicate=true). The created deal, same shape as a query_deals row.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYesDeal name, e.g. "Acme - pilot". Usually the company name.
notesNoFree-text notes.
stageNoPipeline stage by name or id (query_deals lists them). Default: the first stage.
amountNoDeal value as a plain number, e.g. 12000.
person_idNoThe contact, as a query_people `id`.
company_idNoThe company, as a query_companies `id`.
new_personNoA contact to add: name plus email or LinkedIn URL. Used instead of person_id.
new_companyNoA company to add: name plus domain. Used instead of company_id.
assignee_emailNoThe owner, an active teammate's email from list_teammates (not an invited one). Default: the user.
allow_duplicateNoCreate even if this person already has an open deal.
expected_close_dateNoYYYY-MM-DD.

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only say readOnlyHint=false and destructiveHint=false, so the description carries the burden of disclosing side effects. It adds valuable behavioral details: the created deal appears on the Deals view for the whole team, creation requires a person or company, and the tool refuses duplicate open deals while naming the existing deal. This goes well beyond the annotations and the schema's individual field descriptions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and front-loaded: purpose and team visibility come first, then the linking/creation logic and duplicate policy, then the return shape. Every sentence contributes either to selecting the tool, passing parameters correctly, or understanding the result. No filler or redundant restatement of the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an 11-parameter creation tool with no output schema and minimal annotations, the description covers the essential context: core semantics, duplicate handling, link-vs-new patterns, and return shape ('same shape as a query_deals row'). It could be more complete by describing the created deal row fields or permission requirements, but it points to query_deals for the shape and the schema handles per-parameter defaults.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so baseline is 3. The description adds cross-parameter semantics not obvious from the schema: 'A deal needs a person or a company (or both)', and explains when to use person_id/company_id versus new_person/new_company. It also gives meaning to allow_duplicate by tying it to the refusal behavior. This is meaningful added value beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description opens with a specific verb and resource ('Create a deal in the user's CRM pipeline') and clarifies this is how a person or company enters the CRM. It differentiates from the sibling update_deal by explicitly saying to 'update that deal instead' when a duplicate open deal exists. An agent can confidently distinguish create_deal from query_deals and update_deal.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Description gives explicit when-to-use: 'this is how a person or company gets into the CRM.' It also provides clear alternatives: link existing people/companies via query_people/query_companies, or pass new_person/new_company; and if an open deal exists, 'update that deal instead, unless the user wants a second one (then pass allow_duplicate=true).' This leaves no ambiguity about when to call this tool versus update_deal.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_message_tag_groupCreate Message Tag GroupAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
modeNo'single' (one tag per message) or 'multi' (several).single
nameYesShort name for the dimension (e.g. "Opening sentence").
tagsYesThe tags to create, each {name, description}. Description guides the classifier, so make it discriminating.
criteriaNoThe classify scope this question tracks — {channel, agent_ids, positions}, each null = all. `channel` is 'linkedin' or 'email' (null = both); `agent_ids` are campaign (agent_tasks) ids from classifiable_campaigns (null = every campaign); `positions` lists the conversation positions to tag — 'first' (each conversation's first message), 'follow_up' (later messages sent before the prospect replied), and/or 'reply' (messages sent after the prospect replied); null = all. `senders` are the teammate emails whose sent messages to tag (null = every teammate); in a shared workspace, default it to the user's own email unless they ask for teammates. classify reuses this scope, so set it to what the question should track. Omit for all.
descriptionNoOptional one-line description of what the dimension captures.

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only say readOnlyHint=false and destructiveHint=false; the description adds the key behavioral nuance that creating a group does not classify anything and that tag descriptions are read by the classifier. It also discloses the return shape. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary is front-loaded with the core purpose, then gives mode guidance, tag guidance, and the follow-up in tight prose; the return format is cleanly separated. No filler or repetition of schema details.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 5-parameter creation tool with no output schema, the description covers the essential semantics (mode, tag descriptions, no classification side effect, follow-up) and supplies the return dict. Criteria is left to the already-detailed schema, which is acceptable given 100% schema coverage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real meaning for mode (mutually exclusive vs several tags with concrete examples) and for tag descriptions (the classifier reads them). It does not elaborate on criteria, but the schema already documents that thoroughly.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb and resource ('Create a message tag group') and defines it as 'a dimension to classify sent outreach along,' which clearly separates it from segment-group creation and from classification itself. The summary also names the follow-up classify_message_tag_group, reinforcing what this tool uniquely does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides concrete mode-selection guidance (single for mutually exclusive tags, multi for several per message) and explicitly states that creating a group does not classify anything, directing the agent to follow with classify_message_tag_group. It does not spell out when to prefer update_message_tag_group or list_message_tag_groups, so it stops short of full when-not/alternatives coverage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_segment_groupCreate Segment GroupAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
modeNo'single' (one tag per person) or 'multi' (several).single
nameYesShort name for the dimension (e.g. "Seniority").
tagsYesThe tags to create, each {name, description}. Description guides the classifier, so make it discriminating.
criteriaNoThe classify scope this segment tracks — {agent_ids, senders}: the campaign (agent_tasks) ids from classifiable_campaigns and the teammate emails whose prospects to tag, each null = all. classify reuses this scope, so set it to what the segment should track; in a shared workspace, default `senders` to the user's own email unless they ask for teammates. Omit for every campaign and teammate.
descriptionNoOptional one-line description of what the dimension captures.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the readOnlyHint=false/destructiveHint=false annotations by disclosing the real costs and commitments: every person in scope gets tagged, at a per-person credit charge, within ~15 minutes of LinkedIn lookup or first contact. It also explains that the classifier reads tag descriptions and the person's profile, which is exactly the behavioral context an agent needs before committing.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the core definition and mode decision, then cost/latency, then the follow-up call. The <returns> block is somewhat verbose for a dict shape, but every sentence otherwise earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the returns block supplies the response shape (id, name, mode, criteria, tags), and the combination of mode semantics, cost, timing, and follow-up tool makes the description sufficient to call this mutation safely and completely.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, so the baseline is 3, but the description adds genuine meaning: it explains why a tag description matters ("the classifier reads it, and reads the person's profile") and frames mode semantically, not just enumeratively. The criteria/scope caveats largely mirror the schema text, so it is not fully additive.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource and immediately defines the domain concept ("a dimension to classify your canonical people along") plus what it creates alongside it (its tags). An agent can distinguish this from sibling group tools like create_message_tag_group or classify_segment_group without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit guidance for choosing between `single` and `multi` mode with concrete examples (seniority vs traits), and names the follow-up tool (classify_segment_group) for tagging people already in scope. It lacks explicit when-not-to-use guidance versus the message-tag-group siblings, but the operational context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

define_sequenceDefine SequenceAInspect

Author (or replace) the campaign's flow sequence — a DAG of outreach nodes. Each node carries its own out-edges as typed fields, so the grammar is the schema: a connection request has accepted / already_connected / timeout slots, a send has replied / follow_up, a silent action and a manual step each have then, a decision has arms, a terminal has none. Derive the DAG from the agent's goal + triggers: one node per step, each natural-language fork a decision, and terminal only for resting end-states (auto-skip, human handoff, done). The flow must be acyclic, every node reachable from start, and every path capped by a terminal (only a terminal may dead-end) — all enforced.

The shape:

  • start (exactly one, the DAG head): {id, label, entry}. Holds prospects no send has reached (label it "Not started"). entry is the single first touch — {to} for an immediate open, or {to, after: {value, unit}} (units w/d/h/m) to hold that long from when each prospect is tracked. The flow fires it on its own: do NOT queue the first sends yourself on a sequenced campaign — track prospects and let the flow open.

  • connection_request node (LinkedIn): {id, label, kind, accepted, already_connected, timeout?, message_templates?}. message_templates holds its note (see below); without one the step's on_enter code or instructions decide (its minted default code sends no note). Routes BOTH send outcomes — accepted (invite taken) and already_connected (a CR onto an existing 1st-degree connection, a no-op), each a target node id; both required. Route already_connected to a terminal by default (surface the existing relationship for opt-in rather than cold-messaging it); point it at the message step only when the user says to reconnect with existing connections. timeout is an optional held advance {delay: {value, unit}, to} (e.g. withdraw after no accept).

  • send node (a repliable send — LinkedIn message / inmail, or an email): {id, label, kind, channel, action, replied, follow_up?, message_templates?}. channel is linkedin or email (so one sequence spans both); action is a LinkedIn verb for channel linkedin or email for channel email. Routes replied (required) so a reply advances the prospect off the node; follow_up is an optional held no-reply follow-on {delay, to}. message_templates holds the step's message (see below).

  • action node (a bodyless LinkedIn action — follow / withdraw / resolve / comment / reaction): {id, label, kind: "action", channel: "linkedin", action, then}. Nothing to reply to; then is its single advance — {to} fires the instant the action's send lands (a bodyless step flows straight into the next node with no hold), or {to, after} to hold first. A resolve action may add notify: true — the visible "View Profile" visit (LinkedIn notifies the person you viewed them), a warm-up touch typically placed later; the default silent resolve just reads profile data to branch on.

  • manual node (a step a HUMAN performs off-platform that the system can't enact — a call, a gift, a recorded note): {id, label, kind: "manual", action_description, then}. No channel. action_description is the fully-authored ask (no send is composed, no agent run boots — the step is purely a note for the user); entry queues it onto the user's home approval list. then is the next node's id (a bare string, not a {to} object — unlike a silent action's then): the prospect advances onto it when the user marks the queued step done, with no timed hold (completion is human-paced). Use manual ONLY when a human must act — never for a step the agent can do itself (that step never happens, since a manual node boots no run): an automatable side-effect (update a CRM, create a task in a connected tool, post a notification) is an automation node, and an automatable outreach touch is an action/send node.

  • automation node (an automated non-send side-effect the AGENT performs — update a CRM record, create a HubSpot task, post a Slack/webhook notification, tag a record): {id, label, kind: "automation", then}. No channel. then is the next node's id (a bare string, like a manual node's). Author the side-effect itself onto this node's on_enter trigger with update_trigger (a prompt, or code for a deterministic one) — the same way a terminal node's hook is authored, not a field here; the enactment run carries it out for each prospect then advances. Unlike manual it boots an agent run and needs no human; unlike a terminal hook it advances. Reach for this when the user wants a step that "does something" automatically mid-sequence and then continues.

  • decision node: {id, label, kind, rule, arms}. rule is the NL fork judgment; arms is a list of {case, to} — each case a non-blank condition, the default authored as one too (e.g. case: "else"). A decision has no timed edge and no bare advance — only arms. A case arm may target any enactable node — a send / action / automation step, another decision, or a manual step — or a terminal: the decision run moves each prospect onto its arm, and entering it fires that node's own on_enter enactment on its next run (a send/action composes its send, a nested decision judges its fork, an automation runs its side-effect, a manual step queues the human's task), never an inline send. Chain a downstream decision when several arms share one judgment (DAG reuse); use compound arms on one rule ("A and B" / "A and not B" / "else") when one prospect needs several independent conditions judged together; target a manual step to hand a judged arm off to a human.

  • terminal node: {id, label, kind}. A resting end-state, no out-edges.

Every timed hold — a start after, a connection_request timeout, a send follow_up, a silent action's held then — is {value, unit}. A d (day) unit counts BUSINESS days — Mon–Fri, skipping Saturday/Sunday — and is the DEFAULT for a day-count delay: a plain "follow up in 3 days" (like "3 business days") is {value: 3, unit: "d"}. Reach for cd (calendar day) ONLY when the user explicitly wants calendar days including weekends. w/h/m (week/hour/minute) are always calendar.

Entering a node fires its enactment: a connection_request / send / action node composes and enqueues its step, a manual node queues its step onto the user's approval list, an automation node runs its authored side-effect and advances, a decision judges its fork and routes each prospect. Run a second channel alongside the first by chaining it off a step's held advance (a CR timeout, a send follow_up, or a silent action's held then) — not a second start entry: a prospect holds one flow position, so start takes exactly one entry.

A send node's message is its message_templates: a one-entry list holding the message itself, with each personalized part a {slot} — a named field ({first_name}, {company}) or a described slot ({a line about their recent post}); the enactment run fills every slot per prospect and, unless the step's prompt lets it rephrase, keeps the rest of the wording. The templates are saved with the step and come back on get_campaign_flow; omit message_templates to leave a step's templates as they are, or pass [] to remove them. To A/B test a step's message, add a second version to the list: prospects reaching the step alternate between the two, and get_message_experiments reports which gets more replies. At current volumes a test takes weeks and only separates meaningfully different messages (a different opener or ask, not a reworded phrase), so propose versions that differ that much. Changing either version's text restarts the test. To end a test, pass just the version you keep. A connection_request node's message_templates holds its note the same way (at most 300 characters, filled per prospect), where "" is a version sent with no note: ["", note] A/B tests a note against none, and get_message_experiments reports which gets more requests accepted. Anything else the run should know for that step (tone, who to mention, what to skip, whether it may rephrase the template, what to write when a slot can't be filled) goes in the step's on_enter trigger prompt via update_trigger — keep the message itself in message_templates, not in that prompt or in workspace.inputs, so the user sees and edits it on the step. When you first set a template on a step whose prompt already carries its message, rewrite that prompt down to the extra instructions. (workspace.inputs stays the home for a flowless campaign's templates and for per-agent data no single step owns — criteria, audience, exclude lists.)

Authoring (or replacing) a sequence on an agent that ALREADY holds untouched prospects enrolls that whole backlog into it — so before defining a sequence on an agent with an existing roster (a monitoring or engager-capture agent especially), confirm with the user that the accumulated prospects should all enter the campaign.

Re-authoring replaces the prior sequence (allowed on a live campaign); a prospect still resting on a dropped node returns under off_sequence_nodes (with a note on re-homing) rather than being lost. The node-owned on_enter triggers reconcile in the same call: every node kept in the new DAG (same id, any kind) keeps its on_enter row and the prompt/code on it, a new enactable node gets a fresh row auto-minted, and only a node dropped from the DAG entirely has its on_enter row deleted. A new resolve / connection_request / follow node is minted with a default deterministic enactment code (it enacts clean with no agent run — edit it for a non-standard step; a connection request's note goes in its message_templates, not the code); every other kind mints code-less. A reused id keeps its existing row — so after changing what that id's node does, review its on_enter content; an unmodified minted default is regenerated for the new kind automatically, but an edited one is left as you wrote it.

ParametersJSON Schema
NameRequiredDescriptionDefault
agent_idYesID of the agent to attach the sequence to.
sequenceYesThe flow DAG (typed per-kind nodes). Validated for structural integrity, reachability, and acyclicity.

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=false / destructiveHint=false; the description adds substantial behavior beyond that — re-authoring replaces the prior sequence, dropped-node prospects return under off_sequence_nodes, on_enter rows are reconciled/deleted per kept-or-dropped node, and minted-default code is regenerated only if unedited. That said, it does not call out auth/permission requirements or rate limits, and 'replace the prior sequence' with per-node row deletion sits somewhat uneasily with destructiveHint=false (though prospects are explicitly preserved).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is front-loaded with the purpose and core derivation rule before the per-kind reference, and nearly every sentence carries operational detail. It is nonetheless very long for a description and repeats some rules (the business-day `d` default is stated more than once, as is the message_templates-holds-the-message rule), so it is dense but not maximally tight.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, but the description explains where results surface (templates 'come back on get_campaign_flow', experiments on get_message_experiments) and covers enrollment side effects, re-authoring reconciliation, and structural validation constraints — everything an agent needs to author the DAG correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, but the description goes far beyond it, defining the semantics, defaults, and interaction rules for nearly every field: the `entry.after` vs bare advance distinction, business-day `d` vs calendar `cd` units, `message_templates` as an input-only A/B list including `['', note]`, decision arms requiring non-blank cases, and where a step's message belongs (message_templates vs on_enter prompt vs workspace.inputs).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening states a specific verb+resource ('Author (or replace) the campaign's flow sequence — a DAG of outreach nodes') and immediately frames the grammar as the schema. It is clearly distinguishable from siblings like update_node (single node), get_campaign_flow (read), and the setup_*_sequence tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit selection rules across alternatives: manual vs automation vs action/send ('Use `manual` ONLY when a human must act... an automatable side-effect is an `automation` node'), when to use a decision vs chained decisions, how to run a second channel (via a held advance, not a second start), and a pre-condition to confirm with the user before enrolling an existing prospect backlog.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_agentDelete AgentA
Destructive
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
agent_idYesID of the agent to delete

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already mark destructiveHint=true, so the safety profile is covered. The description adds useful behavioral context beyond annotations: the record is retained internally for a short window, the agent stops running, and confirmation is mandatory. This helps the agent understand recoverability and side effects.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is short and front-loaded: it opens with the core action, immediately states the behavioral consequences, and places the critical confirmation requirement before the return note. No filler or redundant restatement.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter destructive tool, the description is complete: it covers the effect, the retention behavior, the user-confirmation requirement, and the return type. There is no output schema, but the return description ('Dict with success status') suffices for this simple operation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% coverage with a single parameter, agent_id, already described as 'ID of the agent to delete.' The prose adds no further semantic detail, so the schema carries the burden; baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Delete an agent') and explains the observable consequences: the user stops seeing it and it stops running. This clearly distinguishes it from sibling tools like create_agent, update_agent, and get_agent_details.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Includes an explicit operational guardrail: 'ALWAYS confirm with the user before calling this,' which tells the agent when not to invoke it casually. It does not name alternatives or exclusion cases, such as using update_agent instead for non-destructive changes, so it misses a top score.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_message_tagDelete Message TagA
Destructive
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
tag_idYesThe tag to delete (from a group's tags list).

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With destructiveHint=true and readOnlyHint=false already in the annotations, the description goes beyond by disclosing that the operation is 'Irreversible' and that the deletion cascades to the tag's classifications, not just the tag record itself. It also clarifies the scope boundary ('The rest of the group is untouched'), which is valuable behavioral context beyond the annotation flags. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary is three tight, high-information clauses: the action, the irreversibility warning, and the scope boundary. The optional returns block is a compact dict example. No filler or repetition; every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter destructive tool, the description is nearly complete: the parameter is fully documented in the schema, the return value is specified as a confirmation dict, and the destructive/irreversible nature and cascade scope are explicit. Minor omissions like permission requirements or implications for messages bearing the tag are not material here, so this is close to complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% — tag_id is described as 'The tag to delete (from a group's tags list).' The tool description adds no additional parameter-level semantics beyond echoing the schema. Baseline 3 is appropriate since the schema fully carries the parameter documentation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb and resource: 'Delete a tag and its classifications.' It explicitly distinguishes itself from the nearest sibling, delete_message_tag_group, by adding 'The rest of the group is untouched,' so an agent can tell this deletes a single tag while preserving the group. This is a clear, differentiating statement of purpose.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies when to use this tool — when you want to remove an individual tag (plus its classifications) but keep the rest of the message tag group — through the phrase 'The rest of the group is untouched.' However, it never names the alternative (delete_message_tag_group) explicitly nor gives a when-not-to-use condition, so the routing guidance is implied rather than stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_message_tag_groupDelete Message Tag GroupA
Destructive
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
group_idYesThe tag group to delete (from list_message_tag_groups).

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true and readOnlyHint=false. The description adds valuable behavioral context beyond the annotations: the operation is irreversible and cascades to delete all tags and classifications in the group. This directly informs the agent about the full destructive scope.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is short and front-loaded: the summary states the action and its scope in one sentence, and the returns section concisely specifies the confirmation dict. Every sentence earns its place with no redundancy or filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple destructive tool with one required parameter, the description is complete: it covers the action, the cascade effect, irreversibility, and the return shape. The schema and annotations cover parameter constraints and safety profile, leaving no critical information missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has 100% coverage for the only parameter, group_id, and already explains that it identifies the tag group from list_message_tag_groups. The description does not add additional parameter-level meaning, so a baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Delete') and a clear resource ('a tag group') and further clarifies scope by stating that all associated tags and classifications are deleted. This distinguishes it from siblings like delete_message_tag and update_message_tag_group without ambiguity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly implies when to use this tool—when an entire tag group and its associated tags/classifications need to be removed. However, it never explicitly names alternative tools like delete_message_tag for single-tag deletion or states when not to use this tool, leaving the guidance implicit rather than explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_segment_groupDelete Segment GroupA
Destructive
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
group_idYesThe segment group to delete (from list_segment_groups).

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true, and the description adds specificity beyond that: 'Irreversible' and the cascading deletion of 'all its tags and classifications'. This tells the agent exactly what will be destroyed, which is valuable beyond the annotation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely compact: a summary sentence states the action, scope, and irreversibility, followed by a minimal returns block. No fluff, and the critical warning is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With one parameter, full schema coverage, a clear destructive annotation, and the description covering scope, irreversibility, and the return value, nothing essential is missing. The returns block even specifies the confirmation payload format.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%: the group_id parameter is described as 'The segment group to delete (from list_segment_groups).' The description adds no parameter-level detail beyond the schema, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Delete') and resource ('a segment group'), and clarifies it also removes 'all its tags and classifications', distinguishing it from sibling tools like delete_segment_tag. The 'Irreversible' warning adds meaningful scope. This is unambiguous and differentiated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context that this tool is for deleting the whole group and its contents, not just a tag or classification. It does not explicitly name alternatives such as delete_segment_tag for partial deletion, but the scope is evident from the cascade description.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_segment_tagDelete Segment TagA
Destructive
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
tag_idYesThe tag to delete (from a group's tags list).

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations already indicate destructive behavior, and the description adds meaningful detail: the deletion is irreversible, classifications are removed, and the rest of the group is unaffected. This goes beyond the annotation hints.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary is two short sentences with no filler, and the return value is provided in a separate compact block. Key facts—irreversibility and scope—are front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter delete action, the description covers the action, irreversible nature, scope, and return format. Combined with annotations and full schema coverage, nothing essential is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and tag_id is clearly documented as 'The tag to delete (from a group's tags list).' The description itself adds no extra parameter details, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Delete a segment tag and its classifications.' It also clarifies the scope by noting the rest of the group is untouched, which distinguishes it from group-level deletion tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives useful scope context but does not explicitly state when to choose this tool over alternatives like delete_segment_group or update_segment_tag. The usage is implied by the description, not spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

detach_from_outreachDetach From OutreachAInspect

Remove a file already attached to a queued outreach email.

recipient_email targets a queued email (pending or awaiting approval); id is the attachment's id as shown on that email in the queue status read. Email queue only, mirroring attach_to_outreach.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesthe attachment's `id` from the queue status read.
recipient_emailYesthe queued email's recipient.

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With annotations only declaring readOnlyHint=false and destructiveHint=false, the description adds meaningful behavioral detail: it specifies that the operation only applies to queued outreach emails and that the attachment id comes from a queue status read. This goes beyond the annotations and clarifies the operational scope without contradicting them.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and front-loaded with the core action. Every sentence adds value: the first states the purpose, and the second clarifies targeting constraints and parameter sourcing without repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple two-parameter tool with no output schema, the description covers the action, the applicable email state, and how to obtain both parameter values. It could have mentioned what happens to the file after removal (e.g., whether it is deleted or just detached), but the essential context for invoking the tool is present.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already documents both parameters with 100% coverage. The description enhances this by explaining that recipient_email targets a queued email and that id is the attachment's id as shown in the queue status read, which gives the agent a precise way to source the required values.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific action and resource: 'Remove a file already attached to a queued outreach email.' It clearly distinguishes this from related tools by noting 'Email queue only, mirroring attach_to_outreach,' so an agent can tell it apart from broader outreach management tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains the target state ('queued email (pending or awaiting approval)') and references the companion tool attach_to_outreach, giving clear context for when this tool applies. It does not explicitly list exclusions or alternative tools for non-queued attachments, but enough guidance is present.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

draft_emailDraft EmailAInspect

Use search_emails or search_linkedin_message_history to find email addresses for prior correspondents. For new contacts whose email you don't have, use find_email. Dict with success, draft_id, provider, and draft details (to, cc, bcc, subject, body)

ParametersJSON Schema
NameRequiredDescriptionDefault
ccNoOptional list of CC recipients, same format as "to"
toYesList of To recipients. Each is an object with required "email" and optional "name" key. ALWAYS pass a list (even for a single recipient), and ALWAYS pass dicts (not bare strings). Examples: - With name: [{"email": "alice@acme.com", "name": "Alice Smith"}] - Without name: [{"email": "alice@acme.com"}]
bccNoOptional list of BCC recipients, same format as "to"
bodyYesEmail body in Markdown, rendered to HTML at send time for every provider (Gmail, Outlook, Superhuman). Write hyperlinks as [text](url) so the link reads as its anchor text. A bare or parenthesized URL left in the text is usually autolinked by the mail client, so it still clicks through — it just shows the raw URL instead of a label, which is what users mean when they say a link "isn't hyperlinked". Single newlines are preserved as line breaks.
mailboxNoEmail address of a connected mailbox (e.g. 'alice@acme.io'). Omit to use the user's default mailbox. When the user has multiple mailboxes connected, ask which to use rather than guessing — surfacing the choice is the agent's job.
subjectYesEmail subject line

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare readOnlyHint=false and destructiveHint=false, so the description doesn't need to restate safety. The description adds valuable behavioral context: it creates a draft (not sending), routes automatically by provider, and requires user confirmation before sending. It also discloses that the draft is created in the connected mailbox. The only minor gap is not detailing what happens on failure or whether drafts are saved to drafts folder, but the core behavior is well covered.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and front-loaded with the core purpose. The summary section is two sentences, and the returns section is one line. It avoids redundancy with the schema. The only slight inefficiency is the parenthetical provider list, but it's useful context. Overall it earns its place without bloat.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 6-parameter tool with 100% schema coverage and no output schema, the description covers the essential context: what it does, provider routing, confirmation requirement, and email-finding alternatives. The returns section describes the output shape (success, draft_id, provider, draft details). The only missing piece is explicit error-handling or edge-case behavior (e.g., invalid recipient), but the schema's detailed parameter docs compensate. This is complete enough for an agent to invoke correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all parameters thoroughly. The description adds value by explaining the 'to' parameter must always be a list of dicts, and the body parameter's Markdown rendering behavior. The schema itself is exceptionally detailed (including examples and rationale for optional name), so the description doesn't need to repeat that. The description's mention of provider routing and confirmation flow complements the schema's parameter docs.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool creates a new email draft in the user's connected email account, names the supported providers (Gmail, Outlook, Superhuman), and notes automatic routing. It distinguishes itself from send_email by explicitly instructing to ask for confirmation before calling send_email, and from draft_reply by focusing on new drafts rather than replies. The verb 'create' and resource 'email draft' are specific and unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit when-to-use guidance: it tells the agent to always present draft details and ask for confirmation before calling send_email, and it names alternatives for finding email addresses (search_emails, search_linkedin_message_history, find_email). This is strong routing guidance that helps the agent decide when to use this tool versus siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

draft_replyDraft ReplyAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
ccNoOptional list of additional CC recipients. Each is an object with required "email" and optional "name". Example: [{"email": "bob@acme.com"}]
bccNoOptional list of BCC recipients, same format as "cc".
bodyYesReply body in Markdown, rendered to HTML at send time — same rules as draft_email: hyperlinks as [text](url), single newlines preserved as line breaks.
email_idYesThe database id of the email to reply to (from search_emails results)
reply_allNoIf true, reply to all original recipients (Reply All). Default: false (reply to sender only).
as_teammateNoDraft the reply on a consented teammate's behalf — pass their email. The draft is created in their mailbox; pass the same teammate to send_email to send it. Gated on that teammate's act-on-behalf setting (stricter than conversation sharing); a teammate who hasn't granted it is rejected. Omit to draft from your own account.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations are minimal, so the description carries the burden. It adds meaningful behavioral context beyond annotations: the draft is created but must be confirmed before sending, and it discloses the return shape (success, draft_id, provider, original email context). It does not fully describe side effects like mailbox persistence, but the core behavior is transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and front-loaded: the action, target, and provider are in the first sentence, followed by the prerequisite workflow and safety rule. The <summary>/<returns> structure is clean and every sentence contributes.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, the description helpfully names the returned fields and gives the essential call sequence. It is complete enough for an agent to invoke correctly, though it could add a bit more about when the draft is actually persisted and what happens if the email_id is invalid.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all six parameters in detail, including examples and edge cases. The description adds little parameter-level meaning beyond telling the agent to pass the email id from search_emails, so the baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb+resource: "Create a reply draft to an existing email thread (Outlook or Gmail)." It clearly scopes the tool to replying to existing emails, distinguishing it from a general compose flow, and the workflow reference to send_email reinforces its role.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit usage context: use search_emails first, pass the resulting id, and always present the draft for confirmation before send_email. This clearly positions the tool in the reply workflow, though it does not explicitly state when not to use it (e.g., for new emails) or name draft_email as an alternative.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

edit_monitor_membersEdit Monitor MembersAInspect

add and remove are LinkedIn profile URLs or vanity slugs. A swap is one call (add=[new], remove=[old]). Both forms of one profile (full URL and bare slug) collapse to a single watched member, so re-adding someone already watched is a no-op. The change takes effect on the monitor's next scheduled run.

Use this instead of re-running setup_linkedin_monitoring whenever the user just wants to tweak who's watched ("also watch Bob", "drop Alice"): setup would force re-passing every existing member, and since each watched profile is billed per run, one dropped URL on the re-echo silently stops watching that person. To change schedule, voice, or the action flags, use setup_linkedin_monitoring (members are preserved there when omitted); adding the very first members or resetting the whole list is setup too. Dict with status ('active'), name, members_count, added, removed, unresolved, and message.

ParametersJSON Schema
NameRequiredDescriptionDefault
addNoLinkedIn profile URLs/slugs to start watching.
nameYesthe monitor to edit (must already exist on this agent).
removeNoLinkedIn profile URLs/slugs to stop watching.
agent_idYesThe agent the monitor lives on.
as_teammateNoEdit a consented teammate's monitor instead of your own — pass their email; agent_id must be one of their agents. Gated on that teammate's act-on-behalf setting; a teammate who hasn't granted it is rejected. Omit for your own.

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description goes well beyond the readOnly/destructive annotations, explaining incrementality, swap semantics, URL/slug collapsing, no-op re-adds, next-scheduled-run timing, and the billing pitfall of re-echoing a dropped URL. This is rich behavioral context that an agent needs to use the tool safely and correctly.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with a summary, behavior details, usage routing, and a returns block. Every sentence carries information; none is filler. The most decision-relevant facts (incremental edits, which tool to choose) are front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a five-parameter tool with no output schema, the description is complete: it documents the return dict's fields, the operational timing, the deduplication behavior, and the sibling tool's role. An agent has everything needed to invoke this tool correctly and to decide when not to.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already covers all five parameters at 100%, which earns the baseline 3. The description adds meaningful semantic detail for add/remove: they accept full URLs or vanity slugs, a swap is one call, and duplicate forms collapse to one member. These semantics genuinely help parameter construction beyond the schema alone.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb phrase ('Add and/or remove watched people') tied to a precise resource ('existing list-mode LinkedIn monitor'), and immediately distinguishes itself from setup_linkedin_monitoring. An agent can tell exactly what this tool does and what it is not.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description is explicit about when to use this tool instead of setup_linkedin_monitoring, gives concrete user-intent examples ('also watch Bob', 'drop Alice'), and states which cases belong to the alternative tool (changing schedule/voice/action flags, first members, resetting the list). This leaves no ambiguity about routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

enrich_linkedin_profilesEnrich Linkedin ProfilesA
Read-only
Inspect

This is the tool for questions about a list of people: filtering a list by connection count or seniority, labelling who works where, or filling in headlines before drafting. experience carries every role with its dates, so it also answers career-history questions — how long someone has been in seat, where they worked before, whether they were promoted internally, who is an alum of a given company — without a separate lookup. It does not touch the user's LinkedIn account, so it neither consumes their daily profile-lookup budget nor carries any account-safety risk — prefer it over per-person lookups whenever you have more than a couple of people to enrich.

Costs 0.1 Sliq credits per profile returned; misses are free. A profile this user enriched in the last 24h is served from cache, so re-calling does not look up or charge again.

Enriching a profile also links it to its primary employer's canonical company, which may need a one-time web-domain lookup: 0.05 credits the first time a given company is looked up (cached after, so it never charges twice for the same company). A batch spanning many unfamiliar employers costs a little beyond the per-profile total; fold that into any estimate you give the user.

How many to run is a spend question: run a list of up to 500 straight away. Past 500, tell the user how many profiles it is and what that costs — the count times 0.1 credits — and wait for a go-ahead before running it; a batch that size also takes several minutes, so say so in the same breath. In a background run there is nobody to ask, so run it and report the spend in your summary.

How to call it is a separate question, and the answer is almost always run_code. A direct return is truncated at 50KB, and one senior profile's career history can be a third of that on its own — so a direct call on a dozen executives shows you two of them, after charging for all twelve, since credits are spent inside the tool before anything is truncated. Only a handful of profiles fit. From run_code nothing is truncated: the rows stay in the sandbox and you print only the filter, count, or summary you need. Call it directly only for a few people whose full profiles you intend to read.

What it cannot tell you: whether the user is already connected to someone, their network distance, or shared connections. Those describe the user's own relationship to the profile and only a LinkedIn-account lookup can answer them — use setup_linkedin_sequence(action_type='resolve') when the decision genuinely depends on connection status. One entry per input, in input order — either a profile dict, or an {'error': ...} entry for a profile that could not be resolved. The error says which case it is: no profile exists for the identifier, or the lookup did not finish and the identifier should be retried.

experience and education cover the person's whole history and are long for senior people — a 25-year career can run 20+ roles. They arrive in LinkedIn's display order, which is NOT sorted by date: roles at the same employer sit next to each other, so the first entry is not reliably the current one. Sort on start_date.get('year') when you need chronology. Consecutive entries at one employer are usually one tenure with internal promotions, but check the dates before saying so — a gap between them means they left and came back, which is a different story to tell. Dates are {'year': 2014, 'month': 'Feb', 'text': 'Feb 2014'}; month is missing when LinkedIn shows only a year, and a date can be empty entirely, so reach for .get() rather than indexing. An end_date of {'text': 'Present'} means the role is current, and someone can hold several at once.

ParametersJSON Schema
NameRequiredDescriptionDefault
linkedin_urlsYesLinkedIn identifiers to enrich — full profile URLs, bare slugs, or encoded provider ids, in any mix. Capped at 1000 per call; split a longer list across calls.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint, so the description carries the rest and does so richly: 0.1 credits per profile returned, misses free, 24h cache, the 0.05 company-domain lookup, the 500-profile approval threshold, background-run behavior, and the 50KB direct-return truncation that still charges for unreturned rows.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Long but well-organized with summary/returns sections and front-loaded purpose. A few passages (cost restatement, repeat of the batching point) could be trimmed, but nearly every sentence carries operational weight for a paid, batch-limited tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers cost/approval gating, invocation strategy, caching, failure modes, and even return-shape caveats (unsorted experience, sparse dates, 'Present' end dates). With no output schema, the embedded returns description fills that gap thoroughly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% for the single `linkedin_urls` param, so the schema already documents accepted forms and the 1000 cap. The description reinforces the mixed identifier types and stresses passing the whole list in one call, adding light value beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource (bulk LinkedIn profile enrichment) and enumerates exactly what fields come back (headline, title, company, location, connection/follower count, employment and education history). It distinguishes itself from sibling lookups by naming `setup_linkedin_sequence(action_type='resolve')` for the connection-status case it cannot cover.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit when-to-use (questions about a list of people — filtering by connection count or seniority, labelling employers, career-history questions), when-not (connection status, network distance, shared connections), and alternative selection (prefer over per-person lookups; use run_code rather than a direct call except for a handful of profiles).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

escalate_to_teamEscalate To TeamAInspect

Sliq stamps the affected user, the surface, and the agent automatically, so write the summary for a teammate with no other context: what the user wanted, what went wrong, and what you already tried. Dict confirming the flag was raised, with a note on what to tell the user next.

ParametersJSON Schema
NameRequiredDescriptionDefault
summaryYesPlain-text description of the problem and what you already tried.
agent_idNoThe agent this concerns. Defaults to the current agent when in one; pass it only to point at a different agent.

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already indicate this is not read-only and not destructive. The description adds meaningful behavioral context: Sliq automatically stamps the user, surface, and agent, and the summary is read by a teammate with no other context. This helps the agent understand downstream consequences and how to write the summary.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is tightly written and front-loaded with the core purpose. Every sentence contributes: the trigger condition, the guidance to escalate rather than change topic, the auto-stamping behavior, and summary content instructions. The returns section is brief and useful.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple two-parameter tool, the description fully covers when to use it, what to write, what happens after, and what the return value looks like. Even without an output schema, the returns note gives the agent enough to know what to expect.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description adds value by specifying what the summary should contain ('what the user wanted, what went wrong, and what you already tried'), which goes beyond the schema's 'plain-text description' and improves the agent's ability to fill the parameter correctly.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action ('Flag a problem to the Sliq team') and a clear trigger condition ('when you're stuck and a human should step in — something you can't resolve'). It clearly distinguishes this from ordinary problem-solving by requiring that the agent has already tried its own tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit when-to-use guidance: after trying and failing to resolve with own tools. It also says to prefer this over switching subject or channel. It doesn't name specific sibling alternatives, but the boundary between escalation and normal resolution is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

estimate_message_tag_classify_scopeEstimate Message Tag Classify ScopeA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
channelNo'linkedin' or 'email' — the value you'd pass as `criteria.channel`. Omit for both.
sendersNoIn a shared workspace, the teammate email addresses whose sent messages to count. Omit for every sender in the workspace. Any email not in the workspace is dropped; if that leaves no valid teammate, the count is zero.
agent_idsNoThe campaign (agent_tasks) ids the group would track, from list_classifiable_campaigns — the value you'd pass as `criteria.agent_ids`. Omit for every campaign.
positionsNoThe conversation positions to count — 'first' (each conversation's first message), 'follow_up' (later messages sent before the prospect replied), and/or 'reply' (messages sent after the prospect replied) — the value you'd pass as `criteria.positions`. Omit for all.
sent_afterNoISO-8601 timestamp; count only messages sent on or after it. Omit for all time.

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already mark this read-only; the description adds further behavioral clarity with 'Nothing is created or charged' and 'as in a real run.' It also clarifies the estimate-versus-exact distinction. No contradiction with annotations exists.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact, front-loaded with the core purpose, and organized with a summary and returns section. Every sentence earns its place, and there is no redundant filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a no-output-schema tool with full parameter documentation, the description supplies the return dict shape, the cost-estimation purpose, the no-side-effect guarantee, and the sibling tool for post-creation usage. An agent has everything needed to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already fully documents all five parameters. The description's mention of 'conversation positions' restates the positions parameter but adds no new meaning beyond the schema's detailed definitions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and object: 'Quote the first classify run of a message tag group that doesn't exist yet' and names the concrete outputs (sent-message count and credited charges). It also distinguishes itself from classify_message_tag_group by noting the latter is for existing groups.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly states when to use it: 'Use it to tell the user the cost before creating the group.' It also tells the agent when to switch to classify_message_tag_group: 'once the group exists.' This gives clear routing guidance with no inference needed.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

estimate_segment_classify_scopeEstimate Segment Classify ScopeA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
sendersNoThe teammate emails whose people the first run would tag, as you'd pass to classify_segment_group. Omit for everyone.
agent_idsNoThe campaign (agent_tasks) ids the segment would track, from list_classifiable_campaigns — the value you'd pass as `criteria.agent_ids`. Omit for every campaign.

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The readOnlyHint annotation is reinforced by the explicit statement 'Nothing is created or charged,' and the description adds the important nuance that this is an estimate, not the exact run figure. This goes beyond the annotation by clarifying the no-side-effect scope and the estimate-vs-exact distinction.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and front-loaded with the core purpose, followed by usage guidance and the return contract. The phrasing 'Quote the first classify run' is slightly awkward, but every sentence adds value and the returns section is appropriately included given there is no output schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The definition covers purpose, when to use it versus the sibling, the no-side-effect guarantee, and the return shape in the returns block. Since there is no output schema, including the returned keys is essential, and this description fills that gap completely for an estimate tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema itself already documents both parameters. The description adds context about campaigns the segment would track, but it does not need to repeat parameter details; the baseline of 3 applies because the schema carries the semantic load.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Quote the first classify run of a segment group that doesn't exist yet' and clarifies it returns a people count and credit estimate. It also distinguishes itself from classify_segment_group by noting that the sibling returns the exact figure once the group exists.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is explicit: use it to tell the user the cost before creating the group, and once the group exists use classify_segment_group instead. This names the exact alternative and the condition that selects between them, leaving no ambiguity.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

exa_find_peopleExa Find PeopleA
Read-only
Inspect

Use AFTER companies have been identified (company-find step in the same agent, user named a target, CRM lookup, etc.) to surface specific decision-makers. Results are always LinkedIn URLs.

Before composing a search, call get_skill_guide('exa_find_people') — it owns the query vs system_prompt split that decides whether results come back useful or noisy (exclusions in query backfire; they belong in system_prompt).

results holds only people who matched your query + system_prompt; non-matches come back in rejected as {name, reason} with no URL. Put exclusions and preferences in system_prompt and act on what returns — don't re-filter the results yourself.

Pass company_domain or company_linkedin_url whenever you have one. A search carrying either is answered by a people database that filters on that identifier, so it cannot return someone at a same-named company. Without one, the search falls to a name-matched provider and everything below applies.

On that name-matched path, a search naming a company drops anyone whose indexed record places them at a differently-named employer — a check the match verdict does not perform, since it weighs the whole search intent at once and passes near-namesakes, returning a founder of 'Acmely' for an 'Acme' search. The check reads the indexed employer, not summary.current_company, which is a model's reading of profile prose and routinely echoes the company you searched for back at you; it also ignores roles known to have ended, so a job someone left is not current.

That path judges against a profile snapshot (refreshed ~weekly), not live reality, so someone who changed jobs since the last crawl can still pass. Two shapes slip through: a longer name that starts with the one you searched ("Acme" admits "Acme Robotics"), and a profile resolved to no indexed record, which falls back to the extracted name. Nothing downstream re-checks — which is why an identifier is worth passing.

Accepts a list of ExaPeopleSearch — pass one for a single-company lookup, or N for a batch, fanned out in parallel (max 5 concurrent, since the name-matched provider has shown 5xx instability under bursty load). Each search routes on its own, so one batch can be served by both providers. Each call retries once on 5xx inside the HTTP client.

An identifier-pinned search matching more people than can be returned at once comes back in its own slot as {error, query} asking you to narrow by titles; the rest of the batch is unaffected. Re-send that one search with titles set.

Costs 0.5 Sliq credits per search that returns without error, whichever provider served it, so an empty result set still charges (the call was made). Free on BYO Exa (a connected Exa key). Soft-fails on insufficient credits: the data is returned and the charge is capped at the remaining balance. List of per-search result dicts in input order. Each is either {results: [...], rejected: [...]} on success, or {error: '...', query: '...'} on failure (after the in-client retry). results entries are {name, url, score, summary} with url always linkedin.com and summary holding name, current_title, current_company. rejected entries are {name, reason} only.

A search pinned by company_domain / company_linkedin_url is filtered on the employer and on titles, so it returns no score, no ordering, and no seniority_level / match_reasoning — those come from the per-profile read that only the unpinned path runs, and are absent rather than guessed. Don't infer seniority from position in the list; read the titles.

On insufficient Sliq credits the batch is refused before running: it raises InsufficientCreditsError rather than returning per-search slots.

ParametersJSON Schema
NameRequiredDescriptionDefault
persistNoDefault True — in an agent, matched people are saved and linked as person rows automatically (see `list_name`). Pass False to return results without saving, for a flow that folds these people into another row instead — e.g. a company-find that nests them under each company row via `record_search_results`, where a standalone person list would duplicate people already shown under their company.
agent_idNoOptional — a specific agent to save the matched people into. Omit it to use the running agent, which is the usual case. Only a throwaway lookup outside any agent returns results without saving.
searchesYesList of `ExaPeopleSearch` objects. Each has its own `query` + optional `system_prompt`. For a single-company lookup, pass a list of one.
list_nameNoShort kebab slug naming the Output-tab list bucket (e.g. 'acme-execs'). When this runs in an agent, matched people are saved and linked as `agent_search_results` person rows automatically — deduped by profile, re-runs update in place; `list_name` names their list, and absent it they land in the 'default' list. Query them by reference with `query_search_results` instead of re-typing URLs from `results`. The `results` return is unchanged either way.
num_resultsNoCandidates Exa retrieves and ranks per search before the match filter runs. Default 10; max 100. Matches cluster at the top of Exa's ranking, so 10 usually holds the right people for a well-formed query; the returned `results` are shorter after filtering. If `results` is thin and `rejected` is long, reword the query (the reject reasons say how) rather than raising this — the tail ranks are lower-relevance anyway.

TDQS

A4.2/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations provide only readOnlyHint=true, so the description carries the entire behavioral disclosure burden — and it over-delivers. It reveals the two-provider routing (identifier-pinned database vs name-matched fallback), the profile-snapshot staleness (~weekly refresh), the near-namesake failure mode ('Acme' admitting 'Acme Robotics'), the reject/rejected contract, retry-on-5xx behavior, the 5-concurrent fan-out limit, and the Sliq credit cost including the empty-result-still-charges and insufficient-credit soft-fail. It even discloses that persistence (saving rows) is the default side effect, which sits alongside a readOnlyHint annotation. The description is consistent with the annotation and adds rich context on top of it — no contradiction.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

This is a wall of prose running to well over a thousand words split across summary and returns sections, far beyond what an agent needs to invoke the tool correctly. While nearly every sentence carries real information, there is redundancy: the Acme/Acmely near-namesake example appears both in the main description and again in the company parameter comment, and the provider-routing explanation is revisited in multiple places. It is front-loaded (purpose, then usage, then details) but not appropriately sized — the density genuinely impedes quick parsing for an agent spending tokens on every tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema and an unusually complex tool (batched searches, two providers, identifier pinning, reject reasons, cost, concurrency, stale data), the description is exceptionally complete. The returns section documents the per-slot success and error shapes, the identifier-pinned variant's missing score/order/seniority fields, the InsufficientCreditsError refusal, and the overflow-error retry path. Nothing an agent needs to call this correctly and interpret its output is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and every parameter already has an unusually detailed schema comment (query's POSITIVE-FORM warning, titles OR-ed substring semantics with 'VP'/'Vice President' examples, company's 'Acmely' near-namesake filtering, persist's save-vs-fold behavior). The description's marginal adds — the provider-pinning distinction, the stale-profile disclosure — are behavioral rather than parameter-specific. With the schema already doing the heavy lifting, the baseline 3 is correct; the description complements but does not substantially extend parameter understanding.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening 'Find people on LinkedIn for one or more company searches' names a specific verb, resource, and scope, and the return contract ('Results are always LinkedIn URLs') pins down the deliverable unambiguously. It also distinguishes itself from its siblings — exa_search_news (news), find_companies_by_tech_stack (companies), search_linkedin_people (direct person search) — by positioning itself as the post-company-find batch person finder. An agent can tell what this does without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage context is explicit and strong: 'Use AFTER companies have been identified (company-find step in the same agent, user named a target, CRM lookup, etc.)', plus a mandatory prerequisite call to get_skill_guide('exa_find_people') that owns the query/system_prompt split. It also explains when to deviate — passing null for company on cross-company and alumni searches, and using persist=False to fold results into company rows via record_search_results. What it never does is name a direct sibling to prefer in an alternative scenario (e.g., 'use search_linkedin_people instead when X'), so the when-not dimension is implicit rather than explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

exa_search_newsExa Search NewsA
Read-only
Inspect

A find-companies News-mode discovery source, used alongside search_news_events and tavily_search — the News-mode skill owns the routing (which signals fan out here). Each query retrieves the top recent news articles matching it, and each article comes back as pre-extracted company rows (name, domain, one-line event), so unlike tavily_search there is no article-extraction step. Covers narrative signals PredictLeads' typed categories miss (AI initiatives, role-scoped exec changes, plant incidents) and fills its zero-result gaps on typed events.

Compose queries in positive form describing the event sought, e.g. "manufacturing companies announcing new plant openings in the United States" — one query per signal, all in one call (the tool fans them out in parallel; one tool call is one LLM round). Don't embed exclusions — vector retrieval has no concept of "not", so naming an excluded term biases results towards it.

Each query retrieves the 10 most relevant recent articles. Pass lookback_days to bound how far back to look (default 30); articles published before that window are excluded server-side. A news scan passes the window from its trigger prompt.

Costs 0.5 Sliq credits the first time Exa news runs in a scan (running it again in the same scan is free, however many queries the batch fans out), plus 1 credit per new company its results add to the list when you record them. A company already in the list from a previous scan is not re-charged. Free on BYO Exa (a connected Exa key). Soft-fails on insufficient credits: the charge is capped at the remaining balance. Dict with companies array and count. Each company row is {company_name, domain, event, headquarters, source_url, title, published_at, query} — domain is lowercased-bare or None when the article didn't reveal one (resolve those downstream before recording), event is a one-line description of what happened, source_url/title/published_at identify the article, and query names the originating query. Rows are deduped by domain across queries (first article wins).

If every query failed with an upstream error and nothing landed, also exa_available: False plus a human-readable error — a source outage, NOT a genuinely empty feed. Partial failure (some queries errored, others returned) stays silent and reports only the companies.

On insufficient Sliq credits the call is refused before running — it raises InsufficientCreditsError instead.

ParametersJSON Schema
NameRequiredDescriptionDefault
queriesYesOne positive-form news query per signal. Pass a list of one for a single-signal lookup.
lookback_daysNo

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only mark readOnlyHint, so the description carries the behavioral burden. It adds substantial detail beyond annotations: parallel fan-out, 10 articles per query, server-side lookback filtering, dedup by domain with first-article-wins, partial vs. total failure behavior, credit costs, and error raising. No annotation contradiction exists.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Though long, the description is structured with a summary and returns section, and every sentence adds operational value: cost model, failure modes, deduplication, and querying rules. It is information-dense without filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description provides the full return shape: company rows, fields, dedup behavior, `exa_available` and `error` on total failure, and credit-refusal behavior. An agent has everything needed to invoke the tool and interpret its results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is only 50% because `lookback_days` is undocumented. The description compensates fully: it explains `lookback_days` default of 30 and server-side exclusion, and gives concrete guidance for `queries` including positive-form composition, one-query-per-signal, and an example.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Discover companies from recent news via Exa's semantic news search.' It clearly differentiates from siblings by explaining that articles come back as pre-extracted company rows, 'unlike tavily_search there is no article-extraction step.'

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly positions the tool within the News-mode routing, names its alternatives (`search_news_events`, `tavily_search`), and states when it is appropriate: it covers narrative signals typed categories miss and fills zero-result gaps. It also gives concrete query-composition rules, lookback guidance, and batching instructions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

fetch_linkedin_messages_with_personFetch Linkedin Messages With PersonA
Read-only
Inspect

Query the local store with search_linkedin_message_history first. Reach for this tool only when that misses, when you need messages older than the roughly two-week local backfill window, or when you must be certain whether a conversation exists — this reads LinkedIn's own synced history, so an empty result here means no such conversation, not merely "outside the local window". Fetched messages are written back to the local store, so a later search_linkedin_message_history sees them too.

This is not a name-lookup tool: pass the person's LinkedIn provider_id, sourced from a prospect row, search_linkedin_connections, or a prior local message — never guessed from a name. On success, a dict with success=True, chat_id (pass to setup_linkedin_sequence with action_type='message' to reply), count, truncated (True when older messages exist beyond the fetch cap), messages — newest first, each with direction ('sent'/'received', or 'unknown' when LinkedIn omits the sender flag), message_datetime (ISO), and content (truncated to 1000 chars) — and connection_request_note: the note on your accepted connection request, which LinkedIn replays into the chat when they accept, as message_datetime + content (null when there's none). It's the invite, not a message you sent them, so it's kept out of messages and count. On failure, success=False and error; an error that mentions reconnecting means the user must reconnect LinkedIn in Settings.

ParametersJSON Schema
NameRequiredDescriptionDefault
as_teammateNoRead a consented teammate's LinkedIn history instead of your own — pass their email. Gated on that teammate's conversation-sharing setting; a teammate who hasn't shared is rejected. Reads via the teammate's own LinkedIn connection and caches the result to their local store. Omit for your own.
provider_idYesThe other person's LinkedIn provider_id (the `sender_provider_id` / `recipient_provider_id` on message and connection rows).

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint, but the description adds substantial behavior: results are written back to the local store so later searches see them, an empty result is authoritative rather than a cache miss, the fetch is capped (truncated flag), and errors mentioning reconnection require the user to reconnect in Settings. These are traits the annotations do not convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded summary of what it does and when to use it, followed by the returns/error contract. No filler sentences; every clause carries operational information (backfill window, write-back, provider_id sourcing).

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Even without an output schema, the <returns> block documents success/failure shape, chat_id handoff to setup_linkedin_sequence, message fields, truncation, and the connection_request_note edge case, including why it is excluded from messages/count. Nothing an agent needs to call or interpret the result is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and already documents as_teammate thoroughly, but the description still adds real value: it identifies provider_id as a LinkedIn provider_id with named sources (prospect row, search_linkedin_connections, prior message) and explicitly forbids guessing from a name. That closes a common misuse path the schema alone does not.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The summary states a specific verb and resource ('fetch the user's full LinkedIn message history with one person') plus the source ('live from LinkedIn via Unipile'). It explicitly distinguishes itself from the sibling local-store tool search_linkedin_message_history, so an agent can route without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives an explicit ordering rule ('Query the local store with search_linkedin_message_history first. Reach for this tool only when...'), names the three triggering conditions (cache miss, older than the ~2-week backfill, need certainty about existence), and states what an empty result means. This is textbook when/when-not guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

fetch_post_engagersFetch Post EngagersAInspect

Visibility follows the user's LinkedIn account: any post it can view works, and private or deleted posts return a plain error. The list is capped at 500 reactions + 500 comments per post; on a bigger post the return flags the cut-off. Data fetched within the last 6 hours is served from the database at no cost; a real fetch counts one action against the shared daily engagement-fetch budget and paces its LinkedIn requests, so a post with hundreds of engagers takes around half a minute — set that expectation with the user before calling this on a big post.

When this runs in an agent, every engager is materialized as a reviewable agent_search_results row (entity_type='person', data carries headline, reaction_value, comment_text, provider_id, source_post_url), rendered in the agent's Output tab and queryable via query_search_results. Re-running updates existing rows rather than duplicating them. Pass list_name to name their list; absent, they land in the 'default' list. By default the full unfiltered list materializes — to act on only a subset (ICP fit, founders only, a specific role), qualify the stored rows first (headline triage via query_linkedin_post_engagements) and queue outreach on just the keepers.

After this returns, filter and slice the full list with query_linkedin_post_engagements using post_id = <post_analytics_id>, then queue outreach with one batched setup_linkedin_sequence call passing each person's provider_id. Send-time resolution skips anyone already connected, so they don't need pre-filtering here. On success, a dict {'success': True, 'post_analytics_id': int, 'author_name': str, 'is_own_post': bool, 'post_text_snippet': str, 'from_cache': bool, 'reactions_stored': int, 'comments_stored': int, 'unique_engagers': int, 'truncated': bool, 'budget': {'used_today': int, 'limit': int}, 'engagers_preview': [first 10 people], 'next_step': str, 'list'?: {'list_name', 'created', 'updated', 'total'}}. On failure, {'success': False, 'error': str} when the post isn't visible to the user's account, the daily budget is exhausted, or LinkedIn actions are paused after a rate limit.

ParametersJSON Schema
NameRequiredDescriptionDefault
persistNoDefault True — in an agent, every engager is saved as a person row automatically (see `list_name`). Pass False to fetch the engagers without saving them as person rows: the raw engagement rows still land and stay queryable via `query_linkedin_post_engagements`, but no `agent_search_results` list is written — for a flow that records only a filtered subset itself, so the full unfiltered list wouldn't also clutter the Output tab.
agent_idNoOptional — a specific agent to materialize the engager list into. Omit it to use the running agent, which is the usual case.
post_urlYesLinkedIn post URL (activity, ugcPost, and share forms all work) or a raw activity ID.
list_nameNoShort kebab slug naming the list bucket (e.g. 'launch-post-engagers'). Absent, engagers land in the 'default' list.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Discloses far beyond the annotations: visibility limits ('private or deleted posts return a plain error'), the 500-reaction + 500-comment cap with a `truncated` flag, 6-hour cache cost exemption, and the shared daily budget. It also details the materialization side effect (agent_search_results rows with entity_type='person' and the specific data fields) and idempotent re-runs ('updates existing rows rather than duplicating'). This is consistent with readOnlyHint=false and destructiveHint=false — no contradiction.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded — core purpose and use case land in the first paragraph — and organized into distinct knowledge blocks (visibility, limits, budget, materialization, workflow). It is long, and some operational advice ('set that expectation with the user before calling') leans toward prose rather than specification, but nearly every sentence carries a distinct fact an agent needs before calling. Slight overlap with the persist/list_name parameter descriptions keeps it at a 4 rather than 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutating tool with 4 parameters, a budget interaction, caching, and a follow-up pipeline, the description covers every decision point: input forms (URL types are in the schema), error conditions (visibility, budget exhaustion, rate-limit pause), return structure via the detailed `<returns>` block, and the exact downstream calls. Nothing an agent needs to invoke it correctly is left to guesswork. The `<returns>` block effectively substitutes for the missing output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents all four parameters and the baseline is 3. The description adds workflow rationale beyond the schema, notably why `persist=False` exists ('records only a filtered subset itself, so the full unfiltered list wouldn't also clutter the Output tab') and the `list_name` default path ('absent, they land in the 'default' list'). That extra context lifts it to a 4.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The summary opens with a specific verb-resource pair ('Fetch the people who reacted to or commented on a LinkedIn post') and immediately states the storage side effect ('store them as queryable engagement rows'). It distinguishes itself from the neighboring query tool by naming `query_linkedin_post_engagements` as the post-fetch filter step, and the use-case phrasing ('everyone who liked this post', 'who commented on her launch post') is unmistakable. The only near-sibling (`queue_linkedin_post_engagement`) isn't explicitly contrasted, but fetch-vs-queue is semantically clear from the text.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit trigger — 'Use when the user wants to turn a post's engagers into leads' — with two concrete example phrasings. It adds cost and latency gates an agent must weigh before calling ('counts one action against the shared daily engagement-fetch budget', 'takes around half a minute') and explicitly instructs setting user expectations on big posts. It closes with a precise follow-up workflow naming `query_linkedin_post_engagements` and `setup_linkedin_sequence` and what to pass each.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

find_companies_by_tech_stackFind Companies By Tech StackA
Read-only
Inspect

Use ONLY when the user explicitly wants to discover companies BY THEIR INSTALLED TECHNOLOGY — e.g. "find DTC brands using Shopify and Klaviyo", "who runs Snowflake AND Looker", "US e-commerce companies using HubSpot". For hiring-signal discovery (companies posting jobs that mention a tech), use theirstack_search instead — that surfaces investment intent, whereas this surfaces installed base.

That installed base is what TheirStack DETECTS from job-posting text, not from crawling storefronts, so it only sees companies that hire and name the tool in their JDs — treat a company's absence as "not detected here," not "not using it" (a storefront-sniffing method like BuiltWith/Wappalyzer would surface more).

For ecommerce-platform tools, results may include the platform's SERVICE PROVIDERS (agencies, ISVs) alongside actual merchants — job mentions don't distinguish "we run on Shopify" from "we sell to Shopify merchants," so inspect domains/industries to filter. Recruiting agencies are always excluded (company_type = direct_employer), matching theirstack_search.

Shares TheirStack's two-part price: 0.5 Sliq credits the first time TheirStack runs in a scan (free on a later same-scan call, including after theirstack_search), plus 1 credit per new company its results add to the list when you record them. Companies already in the list are not re-charged.

In chat, STATE THE COUNT AND THE COST in the same reply as the results — every time, without stopping to ask first. Use the user's number when they gave one, otherwise the default: "pulled 25 companies — up to 25 credits (new ones only); say if you want more, max 100." Rows here are companies 1:1 and billing is per new company recorded, so that figure is an upper bound — never quote it as a price. Because this endpoint pages, prefer one page at the user's number over silently walking offset past it — every extra page is more credits.

The tool resolves each technology name to a TheirStack catalog slug (calling /v0/catalog/technologies per name; popularity tiebreak; exact name match wins), then queries /v1/companies/search with company_technology_slug_and so EVERY supplied technology must be present on the returned company. { "companies": [ { "id": str, "name": str, "domain": str | None, "industry": str | None, "employee_count": int | None, "country_code": str | None, "linkedin_url": str | None, "technologies_found": [ {"slug": str, "name": str, "confidence": str, "jobs": int, "last_date_found": str}, ... ], }, ... ], "count": int, # number of companies in this page "total_matches": int, # universe size for this query (or None) }

On upstream failure (timeout / 5xx / connection error), returns {"companies": [], "count": 0, "theirstack_available": False} so the agent can read the flag and degrade gracefully.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoCompanies this ONE call returns, 1-100 (values outside are clamped), default 25. Pass the user's number when they named one.
offsetNoNumber of results to skip for pagination. Pass `limit`, 2*`limit`, ... to fetch subsequent pages. Re-run with the same filters to get a stable order. Each page is billed like a fresh pull, so page only when the user asked for more.
industryNoLinkedIn-style industry names the company must match (e.g. ["Retail", "Higher Education"]). Values are validated against TheirStack's canonical catalog (431 industries) — wrong variants like "Architecture & Planning" raise ModelRetry with close-match suggestions.
confidenceNoTheirStack tech-detection confidence band. Pass ["high", "medium"] to drop one-off / stale mentions. Defaults to None (all confidence levels). Valid values: "high", "medium", "low". If a confidence-filtered search returns zero matches for a technology that plausibly has users, retry without `confidence` and judge per-company strength from `technologies_found[].confidence` instead — TheirStack's confidence aggregates are intermittently incomplete for some technologies.
company_cityNoCity/state substrings the company HQ must match (e.g. ["Atlanta", "Chicago", "Washington"]). Case-insensitive substring match, OR-combined across the list. Do NOT include the `(?i)` flag — TheirStack rejects it on this field. Pair with `country_codes` to keep matches scoped.
technologiesYesHuman product names — e.g. ["Shopify", "Klaviyo"]. Use canonical product names; names with no catalog match raise ModelRetry, and ambiguous names resolve to the most popular match. AND semantics: the company must use ALL supplied technologies. Max 10 per call.
country_codesNoISO alpha-2 HQ country filter. Pass ["US"] to scope to US companies — usually the right default.
min_revenue_usdNoMinimum company annual revenue in USD (e.g. 10_000_000 for $10M+).
industry_excludesNoIndustry names to exclude. Same canonical-name validation as `industry`.
max_employee_countNoMaximum company headcount. Defaults to None; consider passing e.g. 5000 if "uses Shopify" alone returns 10K+ matches dominated by enterprise outliers.
min_employee_countNoMinimum company headcount.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only provide readOnlyHint, so the description carries the full burden — and it excels. It discloses the detection method (job-posting text, not storefront crawling), the meaning of absence ('not detected here'), the two-part pricing model, the chat-response requirement to state count and cost, the failure return shape, and the slug-resolution mechanics. All of this goes well beyond what the annotations state.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long, but well-structured and front-loaded: a one-line summary, then usage rules, caveats, pricing, and mechanics in logical order. Every section carries essential information for correct invocation; the density is justified by the tool's complexity, though it could be trimmed slightly without losing value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite lacking a formal output schema, the returns block documents the exact JSON shape including the failure case (`theirstack_available: False`), and the description covers pricing, detection caveats, exclusions, pagination behavior, and chat-side instructions. Nothing an agent needs to invoke this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents every parameter. The description still adds meaningful context: AND semantics for `technologies` ('EVERY supplied technology must be present'), per-page billing implications for `limit`/`offset`, and guidance to prefer the user's requested number for `limit`. This exceeds the baseline without duplicating the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a clear verb+resource statement: 'Find companies whose technographic profile matches a target tech stack.' It then explicitly differentiates from the closest sibling, `theirstack_search`, by contrasting 'investment intent' vs 'installed base,' so an agent can confidently tell the two apart.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit 'Use ONLY when...' conditions with concrete examples ('find DTC brands using Shopify and Klaviyo'), names the exact alternative (`theirstack_search`) for the hiring-signal case, and provides exclusions (recruiting agencies excluded, don't quote the figure as a price). This is textbook when-to-use vs when-not-to guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

find_emailFind EmailA
Read-only
Inspect

Costs 1 Sliq credit per verified email; a BYO-Apollo key makes the Apollo leg free. The default chain runs MoltSets first and fills its misses with Apollo, so a contact that comes back {'error': ...} has been tried by both providers and is final — report those gaps and move on rather than re-running them.

When only one provider ran — a BYO-Apollo user's default, or an explicit 'apollo' pin — the other is still untried, and the result says so. That paid lookup is the user's call: in a background run with no one to answer, report how many were missed and stop; in an interactive chat, tell them the count and offer a MoltSets backfill (1 credit/hit), re-calling with provider='moltsets' on just those contacts only after they say yes.

A contact this user already resolved in the last 24h is served from a cache, so re-calling does not look up or charge again. One entry per input contact, in input order — either a hit dict (email, email_status, name, title, linkedin_url) or an {'error': ...} miss — followed by any warnings.

Each verified email is also written for you onto the person's canonical profile (the Email column on the Output tab) and onto their prospect row in your campaigns if it had none — you do not match or persist emails back onto rows yourself.

ParametersJSON Schema
NameRequiredDescriptionDefault
contactsYesPeople to look up. Each must have name plus at least one of linkedin_url, company_name, or company_domain.
providerNoLeave unset (default) to let the chain pick — almost always right; don't pin a source the user didn't ask for. Set 'apollo' or 'moltsets' to pin a single source: 'apollo' runs Apollo alone (free with BYO Apollo, 1 credit/hit on Sliq's key); 'moltsets' runs MoltSets alone (1 credit per hit). Use 'moltsets' for the backfill the user approved after a run where only Apollo went out. Pinning skips the fallback, so coverage may be lower.

TDQS

A3.8/5.0
Behavior1/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description directly contradicts the readOnlyHint=true annotation by stating that verified emails are 'written for you onto the person's canonical profile' and 'onto their prospect row in your campaigns.' This is a state-modifying side effect, so per rubric the score is 1 and the contradiction must be flagged. Apart from that, the description is transparent about costs, caching, and provider fallback behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but front-loaded with the core purpose and organized into summary and returns sections. Every paragraph carries operational necessity, though some sentences are dense enough that a bit of trimming could improve readability. Overall efficient for the tool's complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Even without an output schema, the description fully defines the return shape, error semantics, side effects, cost model, cache behavior, and provider fallback logic. An agent has everything needed to invoke the tool correctly and interpret results, including when to stop and ask the user.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds meaningful semantics: the contacts parameter's single-vs-batch usage, parallel lookup behavior, provider pinning effects, and the cache/credit implications of choosing provider or leaving it unset. These details go well beyond the schema's field descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Find email addresses for one or more people.' It clearly distinguishes itself from sibling tools like find_phone_number and find_linkedin_url by the object being found, and further clarifies batch versus single lookup without ambiguity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides strong operational guidance: pass single contacts as a one-entry list, batch contacts for parallel lookup, don't re-run finalized misses, and only run backfills after explicit user approval. It does not explicitly compare against alternative tools or state when to prefer find_email over apollo_enrich or other siblings, so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

find_linkedin_urlFind Linkedin UrlA
Read-only
Inspect

For email outreach where the agent might later add LinkedIn touches, prefer setup_email_sequence(enrich=True) over calling this tool separately — enrich folds the lookup into the queue boundary so the resulting tracking row carries both identifiers.

Costs 1 Sliq credit per verified hit. Free when the user has connected their own Apollo key. Dict with success, linkedin_url, and person fields when verified; {success: False, error: ...} when Apollo returns no match; a 429 throttle adds rate_limited: True (transient — retry later, not a permanent miss).

ParametersJSON Schema
NameRequiredDescriptionDefault
nameNoOptional full name to narrow the Apollo match.
emailYesEmail address to look up.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description discloses real behavioral costs: 'Costs 1 Sliq credit per verified hit. Free when the user has connected their own Apollo key.' It also explains the 429 throttle behavior and distinguishes transient rate-limiting from a permanent no-match result. This meaningfully exceeds what the annotation alone conveys.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core purpose is front-loaded in the first sentence, with use cases, alternatives, cost, and return behavior following in a logical order. The phrasing is mostly efficient, though the 'queue boundary' explanation in the setup_email_sequence suggestion is somewhat jargon-heavy and could be clearer.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the <returns> section does its job by defining success shape, no-match shape, and the rate-limited shape. The description covers inputs, provider, cost, alternatives, and error semantics, making it complete for an agent to decide when to call it and what to expect.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents both parameters: email and optional name for narrowing the match. The description restates email as the lookup key but adds no new parameter-level meaning beyond the schema, matching the baseline expectation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Find a LinkedIn profile URL for a person given their email, using Apollo.io.' It also identifies itself as 'The reverse of find_email', which clearly distinguishes it from a key sibling tool without requiring schema inspection.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives concrete usage contexts: deduping a later LinkedIn step against an email step, and supplying the URL required by find_phone_number. It also explicitly names an alternative, setup_email_sequence(enrich=True), and says to prefer it for email outreach when LinkedIn touches may follow. This is strong, actionable routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

find_phone_numberFind Phone NumberA
Read-only
Inspect

Pass a list of LinkedIn profile URLs — one entry for a single person, all of them at once for a batch (they are looked up in parallel, costing one tool call against the loop guard, not N). Returns one result per URL in the same order. Airscale brokers providers like RocketReach. Persists nothing — use the returned numbers however the agent needs them (e.g. tell the user, or stash them on prospects via update_prospect).

A LinkedIn profile URL is the only accepted input — this tool cannot look a person up by email or name. When a record (a HubSpot/CRM contact, a prospect row, a spreadsheet line) has no LinkedIn URL but does have the person's email, first call find_linkedin_url(email=...) to resolve one, then pass that URL here. Apply this resolve-then-lookup step to every record, not just the first; skip the phone lookup for any person you cannot get a URL for.

Costs 8 Sliq credits per number found, when run on Sliq's shared Airscale account. Misses (no number on file) and upstream failures are free. A worst-case check runs up front on the Sliq path: the whole batch is refused unless the balance covers 8 credits per URL (any URL could be a hit). So no API call is ever spent on a lookup that can't be billed, and a user low on credits is told to top up or connect their own key. If the user has connected their own Airscale key (BYO, via the integrations page), lookups run against that account instead — no Sliq credit gate and no Sliq credit charge. A list of result dicts in input order. Per hit: {status: 'success', found: True, phone_number, phone_numbers, provider, linkedin_url, credits_charged: 8} (0 on the BYO path) — phone_number is the first number, phone_numbers the full list. Per miss: {..., found: False, phone_number: None, phone_numbers: [], credits_charged: 0}. A malformed (non-LinkedIn) URL or an upstream failure on one URL becomes a per-item {found: False, error, credits_charged: 0} in that slot — it never aborts the rest of the batch. If the up-front worst-case credit check fails, it raises InsufficientCreditsError (no lookups are attempted).

ParametersJSON Schema
NameRequiredDescriptionDefault
linkedin_urlsYesLinkedIn profile URLs, e.g. ["https://www.linkedin.com/in/janedoe"].

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description discloses parallel batch behavior, one-tool-call loop-guard behavior, order preservation, persistence behavior ('persists nothing'), credit costs, the up-front worst-case balance check, InsufficientCreditsError, and per-item error isolation. This far exceeds what annotations alone provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but densely informative; the core summary is front-loaded and the cost/credit details are structured into clear sections. Every sentence carries operational meaning an agent would need before calling or deciding not to call.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With only one parameter and no formal output schema, the description fully compensates by specifying return shape per hit/miss/error, failure handling, cost implications, BYO-key behavior, and the prerequisite LinkedIn-URL constraint. Nothing an agent needs for correct invocation is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already documents linkedin_urls with an example (100% coverage), and the description adds important semantics: one URL per person, single or batch, parallel execution, one tool call vs N, and same-order results. It doesn't go much beyond that, but the added batch and ordering semantics are valuable.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific action (find phone numbers) and the exact input (LinkedIn URLs), and explicitly contrasts with what it cannot do (look up by email or name). This clearly distinguishes it from siblings like find_linkedin_url and find_email.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit when-to-use context (when you have LinkedIn URLs), explicit exclusions (no email/name lookup), and an explicit alternative workflow: call find_linkedin_url(email=...) first when only an email is available. It also instructs to repeat resolve-then-lookup per record and skip if no URL can be obtained.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

find_warm_intro_pathsFind Warm Intro PathsA
Read-only
Inspect

Pass targets as a list of LinkedIn identifiers — profile URLs, username slugs, or provider_ids — for the people the user wants to reach. Runs in the background, pacing 1-5 minutes between targets to stay within LinkedIn's safety limits; results land as one row per target under list_name in the agent's entity store — read them with query_search_results(agent_id, where_clause="list_name = '<list_name>'"). Each row carries the mutual connections — the shared 1st-degree connections who can introduce the user.

For each target the search returns at most 10 mutual connections. A target that comes back with all 10 may have more the search didn't surface — treat that target's list as a sample rather than the complete set, and say so when you report it.

list_name is the bucket the target rows land in, rendered as its own Output sub-pill. Pass a short descriptive slug naming this set of targets (e.g. 'acme-intros', 'series-a-leads'); distinct slugs let one agent hold several independent warm-intro searches, and reusing a slug accumulates into one list.

Each target costs 1 profile lookup + 1 LinkedIn search against the daily budgets (~50-60 lookups for an established account, a quarter of that while a new one ramps up; the LinkedIn search budget is shared with all people searches), plus 1 credit only when a mutual connection is found for that target (0 credits otherwise). An already-connected target with shared connections still costs a credit — the warmer path is still worth surfacing; only a target with zero shared connections is free. Targets beyond today's budget are deferred — re-run tomorrow to continue. Confirm with the user before passing a large list. Only one warm-intro search can run at a time per account. A status dict in one of three forms. {'success': True, 'status': 'running', ...} on kickoff {'success': True, 'status': 'queued', ...} when another warm-intro search already holds the account's single slot — this one is queued behind it and starts automatically when the active one finishes {'success': False, 'error': str} on validation / budget failure

ParametersJSON Schema
NameRequiredDescriptionDefault
targetsYesLinkedIn identifiers (profile URLs, slugs, or provider_ids) of the people to find warm-intro paths to.
list_nameYesshort kebab slug naming this set of targets; distinct slugs render as separate Output sub-pills. Required.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description goes well beyond the readOnlyHint annotation by disclosing background execution, pacing (1-5 minutes between targets), LinkedIn safety limits, daily budget behavior, credit costs per target, deferral of targets beyond budget, and the single-slot concurrency constraint. It also explains the 'sample vs complete set' caveat for targets returning 10 mutual connections. This is rich behavioral context that annotations alone do not provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but information-dense, with clear sectioning via <summary> and <returns> tags. It front-loads the core purpose and target format, then covers operational details. While it could be tightened, every sentence carries operational or behavioral information an agent needs; the length is justified by the tool's complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is complete for a complex background tool: it covers input format, output location and format, result interpretation caveats, cost/budget behavior, concurrency, and failure modes. The <returns> section documents the three possible status dict forms. Nothing an agent needs to call this tool correctly and interpret its results is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents both parameters. The description adds meaningful context beyond the schema: it explains what kinds of identifiers are accepted (profile URLs, username slugs, provider_ids), how list_name is used as a bucket/slug for grouping results, and that reusing a slug accumulates into one list. This adds value beyond the schema's basic descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('find'), a specific resource ('warm-intro paths' via the user's 1st-degree LinkedIn connections), and the target input format. It clearly distinguishes itself from sibling tools like search_linkedin_people or search_linkedin_connections by focusing on the warm-intro path use case.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly explains when to use this tool (to find warm-intro paths to target people), how to pass targets, how to read results via query_search_results, and what to do with partial results (treat 10-mutual-connection targets as a sample). It also gives operational guidance: confirm with the user before large lists, re-run tomorrow for deferred targets, and only one search at a time per account.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

generate_monitored_post_commentGenerate Monitored Post CommentA
Destructive
Inspect

Prefer this over queue_linkedin_post_engagement (with your own text) when acting on a post from the agent's monitored-posts feed. Pass a row id from query_monitored_posts. Only posts with no queued comment are eligible ('skipped', 'skipped_no_credit', 'engagers_only', 'surfaced'); already-commented, queued, or user-rejected posts are refused. A queued-status dict in one of three forms. {'queued': True, 'status': 'queued', 'draft_comment': str, 'message': str} on success (1 credit charged) {'queued': False, 'deduped': True, 'message': str} if a comment is already queued {'queued': False, 'error': 'not_found'|'not_eligible'|'out_of_credits'|'no_draft'} otherwise

ParametersJSON Schema
NameRequiredDescriptionDefault
as_teammateNoDraft on a consented teammate's monitored post instead of your own — pass their email; the 1-credit charge lands on their account. Gated on that teammate's act-on-behalf setting; a teammate who hasn't granted it is rejected. Omit for your own.
monitored_post_idYesThe `id` of a monitored_posts row (from query_monitored_posts).

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations (readOnlyHint=false, destructiveHint=true), the description discloses the concrete side effects: it charges 1 credit, flips the post's feed status to 'queued', refuses ineligible posts, and returns one of three distinct state forms. It also notes the 'same Sliq drafting the monitor sweep uses' for consistency. This is rich behavioral context that the annotations alone do not provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The text is dense but well-organized: a first-sentence summary, an explicit routing rule, eligibility/refusal conditions, and a structured returns block. Every sentence earns its place, especially since there is no output schema and the returns block is the sole documentation of the tool's return contract.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with two parameters, no output schema, and meaningful side effects, the description covers all essentials: what it does, when to use it, which inputs to pass, which states are refused, credit/status consequences, and all possible output shapes. An agent has everything needed to call it correctly and understand the outcome.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description adds extra selection semantics by specifying which row statuses make a monitored_post_id valid ('skipped', 'skipped_no_credit', 'engagers_only', 'surfaced') and by clarifying the credit-charge consequence. This goes beyond the property descriptions, though the schema already covers the core parameter meaning well.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb phrase — 'Draft a comment on demand for one monitored post ... and queue it for approval' — naming both the resource (monitored post) and the action. It distinguishes this tool from its nearest sibling by name, explicitly contrasting it with queue_linkedin_post_engagement and tying it to query_monitored_posts.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives a direct routing rule: 'Prefer this over queue_linkedin_post_engagement ... when acting on a post from the agent's monitored-posts feed.' It also states that the caller should pass a row id from query_monitored_posts and enumerates the eligible statuses and refusal conditions, leaving no ambiguity about when this tool applies.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_agent_detailsGet Agent DetailsA
Read-only
Inspect

list_agents returns only one-line summaries — call this to get the full picture for a specific agent. Run-level history is returned here as recent_runs; each entry carries its run id, which you pass to get_run_transcript for that run's full tool-by-tool transcript. Agent-specific operational learnings are returned as a top-level learnings list (managed via append_learning). Dict with agent fields, workspace, learnings, recent_runs, recent_runs_truncated (True when older runs remain past this page of recent_runs), and tracking_summary. When the agent has prospects, tracking_summary.funnel carries the derived outreach rollup the agent workspace shows — per-channel funnels, connection acceptance_rate, reply_rate, and the people-level lead_funnel — so read rates and lead buckets from there rather than deriving them off the raw stage counts. lead_funnel.reached holds the numbers on the funnel's rungs (per rung, everyone who got at least that far); its other stage keys count only the people currently at that stage. mcp_changes lists changes made outside Sliq by the user's MCP clients (such as Claude) calling Sliq tools directly, newest first, one entry per burst of calls to one tool: id, tool_name, client_label, count, first_at, last_at, and latest_arguments (the burst's latest call's arguments). Pass an entry's id as change_id to get_mcp_change_calls to read every call in the burst.

ParametersJSON Schema
NameRequiredDescriptionDefault
agent_idYesID of the agent to load
recent_runs_limitNoHow many of the most recent runs to include (default 10, max 50)
recent_runs_offsetNoRuns to skip for paging the recent_runs list (default 0). When `recent_runs_truncated` is True, re-call with recent_runs_offset += recent_runs_limit to page into older runs.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, so safety is covered. The description adds substantial behavioral context: pagination via `recent_runs_truncated`, the meaning of `recent_runs`, how to handle `mcp_changes`, and how to interpret `tracking_summary` funnels. It does not contradict the read-only annotation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with a clear summary and then uses a structured returns section. It is lengthy, but much of the length substitutes for a missing output schema by explaining return fields and pagination behavior. The structure is effective, though not maximally concise.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given that there is no output schema, the description fully documents the return shape, paging behavior, truncation signal, and how to interpret nested summaries like `tracking_summary.funnel`. It also connects related tools (`get_run_transcript`, `get_mcp_change_calls`, `append_learning`). Nothing essential for correct invocation or interpretation appears missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the description largely repeats the schema's paging guidance for `recent_runs_offset` rather than adding new parameter meaning. It clarifies return fields, but those are not input parameters. The baseline score of 3 is appropriate when the schema already documents all three parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: load full details for a single agent, including workspace, learnings, recent runs, and tracking summary. It explicitly distinguishes itself from `list_agents` and routes run-level transcript access to `get_run_transcript`, so an agent can tell it apart from siblings without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It names the exact user intent ("when the user asks about a specific agent's progress, history, or contents") and the alternative tool (`list_agents`) with the condition that selects this one. It also clarifies how to continue to run transcripts, leaving little to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_campaign_flowGet Campaign FlowA
Read-only
Inspect

sequence is the authored DAG (null for a flowless campaign): sequence.start is the origin node and sequence.nodes the body nodes, each with its id, label, and kind (start / connection_request / send / action / manual / automation / decision / terminal) plus that kind's typed routing — a connection_request's accepted / already_connected / timeout / message_templates (its note), a send's channel / action / replied / follow_up / message_templates (two while it A/B tests a second version), a silent action's channel / action / then, a manual node's action_description / then, an automation node's then (its side-effect is authored on its on_enter trigger), a decision's rule / arms, a terminal (a sink). Timed edges carry {value, unit} (unit w/d/h/m). A connection_request node is always LinkedIn (its channel/action are implicit in its kind); the resolve stage is the action == 'resolve' silent-action node — it looks each profile up, then the campaign flows onward. Walk sequence to see which step feeds which and what gates each hop; join it with nodes by node id for per-node occupancy.

nodes is the per-node occupancy map, keyed by node id: by_status, current, blocked, pending, removed, and off_sequence. It covers every sequence node plus any foreign id the queue throughput or the cursor carries. The start node's current counts every prospect not yet routed onto any node. removed counts prospects removed from the campaign at that node (skipped on its channel, or on every channel for a node without one): they still rest there, and are left out of current, blocked, and pending. off_sequence is True for a node id prospects rest on that the current sequence no longer defines — a position an earlier flow edit stranded — surfaced so those prospects stay visible (such an id is absent from sequence, so read its counts here).

pause is the per-scope pause breakdown (null for a non-outreach agent): campaign.paused holds every action node; linkedin.reason (needs_reconnect / auto / manual, with until on an auto rate-limit pause) holds every LinkedIn action node while email stays live; and connection_request.until holds only connection_request nodes. Read it to answer "why is step 3 not firing" per node — a paused scope explains a node with pending occupants but no throughput. Dict with sequence (the authored DAG as stored, or null for a flowless campaign), nodes {node_id: {by_status, current, blocked, pending, removed, off_sequence}}, and pause (per-scope pause breakdown; see above). blocked is how many resting prospects have a blocked enactment (see report_enactment_blocked; clear one for retry with retry_blocked_enactment); pending how many rest on an action/manual/decision node whose step hasn't run for them yet — for a manual node, awaiting the human who performs it — no intervention needed, unlike blocked. Null sequence and empty nodes for a flowless campaign with no queue activity.

ParametersJSON Schema
NameRequiredDescriptionDefault
agent_idYesID of the agent (campaign) to read.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With only readOnlyHint=true in annotations, the description adds substantial behavioral context: null `sequence` for a flowless campaign, `off_sequence` marking stranded node ids, `removed` prospects still resting but excluded from current/blocked/pending, and `pause` explaining why a node has pending occupants but no throughput. These are exactly the caveats an agent needs before interpreting the live view.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but front-loads the summary and use cases before diving into DAG and occupancy semantics. Much of the length is necessary because there is no output schema, though the `<returns>` block repeats some pause details already explained above.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema and a complex nested return shape, the description fully documents `sequence`, `nodes`, and `pause`, including null and empty cases, node kinds, routing fields, and the distinction between blocked and pending. An agent has everything needed to call and interpret the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already defines `agent_id` as 'ID of the agent (campaign) to read.' The description adds meaningful scope beyond that: the parameter can be any of the user's campaigns and is not limited to the agent in context. This is a useful clarification, though not a syntax or format detail.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Read the live Campaign Flow for an agent: the authored `sequence` DAG plus, per node, how many prospects currently rest there.' It distinguishes this view from sibling tools by tying it to the Campaign Flow tab and to diagnostic questions about prospect location and step firing.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit use cases: answer 'where are my prospects', 'how many are stuck at the connect step', or trace the campaign's shape. It also clarifies scope: 'Pass any of the user's campaigns; it is not limited to the agent in context.' It does not name when to choose this over sibling tools like get_node_history, so it stops short of full when-not/alternative routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_chat_messagesGet Chat MessagesA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum number of messages to return (default 50, max 100). Most recent messages are returned.
offsetNoSource messages to skip for paging (default 0). offset counts the underlying session messages (newest first), so re-call with offset += limit to page into older history while `has_more` is True.
session_idYesThe ID of the chat session to retrieve messages from.

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, and the description adds genuinely useful behavior: it returns a dict with title, message count, has_more, and ordered messages, plus notes that has_more indicates older messages remain. No contradiction. It doesn't discuss failure modes, but with readOnly covered and return shape documented, 4 is appropriate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Short, front-loaded summary followed by a compact returns spec. Every sentence earns its place; there is no filler or repetition of schema details.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite no output schema, the description documents the return dict and paging semantics. For a simple 3-param read tool with a required session_id and detailed schema descriptions, nothing essential is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema covers all three params in detail (limit default/max, offset paging, session_id), so baseline is 3. The description adds value by explaining where session_id comes from (search_chat_history), which is not in the schema and helps correct invocation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific operation ('Retrieve readable messages') on a clear resource ('a past chat session'), and the workflow mention of search_chat_history distinguishes it from the sibling search tool. An agent can tell this is the read-after-search step without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly instructs to call search_chat_history first to obtain the session_id, then use this tool to read the conversation. This gives a concrete when-to-use and names the relevant alternative.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_credit_usageGet Credit UsageA
Read-only
Inspect

Current Sliq credit balance, 30-day spend by agent, and — for a workspace member — the shared team block.

Returns {'balance': float, 'usage_by_agent': [{'agent_id', 'title', 'credits_used', 'is_agent'}, ...], 'team': None | {'workspace_name', 'your_role', 'seats_used', 'seats_active', 'seat_limit', 'per_member_spend': [{'user_email', 'credits_used_30d', 'is_you'}, ...]}}. balance/usage_by_agent are the same rollup the Billing page renders; spend not tied to an agent groups by activity label (e.g. 'Standalone chat', 'Data extraction'). team is None for solo users and otherwise mirrors the Billing team card: seats_used counts invited+active seats (spoken for), seats_active only active ones. per_member_spend excludes removed members and unattributed charges, so it does not sum to pool spend — it is spend over the last 30 days.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint. The description adds substantial behavioral context: team is None for solo users, seats_used vs seats_active meaning, per_member_spend excludes removed members and unattributed charges, and unattributed spend groups by activity label. This goes well beyond the annotation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every sentence earns its place by explaining return semantics, aliases, or edge cases. The purpose is front-loaded, and the return structure is compactly presented before deeper caveats.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description must fully document the return shape, and it does: nested structures, None cases, counting semantics, and non-summation caveats are all covered. For a zero-parameter read-only tool, nothing needed to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so there is no parameter semantics burden to carry. Per the zero-parameter baseline, the description appropriately spends no space on inputs that do not exist.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: it retrieves the current Sliq credit balance, 30-day agent spend, and the workspace team block. This clearly distinguishes it from sibling get_* and check_* tools by data domain rather than relying on the name alone.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context for when this tool is relevant: whenever credit balance, spend by agent, or team seat/credit data is needed. It does not explicitly name alternatives or exclusions, but the scope is specific enough that an agent can route to it confidently.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_email_attachmentGet Email AttachmentA
Read-only
Inspect

Provide email_data_id + attachment_id (from search_emails results). Dict with filename, mimeType, size, and url (CloudFront URL for the file)

ParametersJSON Schema
NameRequiredDescriptionDefault
attachment_idNoThe attachmentId from the attachments metadata
email_data_idNoID of the email_data record (from search_emails)

TDQS

A3.5/5.0
Behavior1/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description says the tool 'upload[s] it to S3', which is a write side effect, while the annotations declare readOnlyHint=true. This is a direct annotation contradiction, making the behavioral signal unreliable for an agent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and well-structured: a short summary block and a return contract. Every sentence earns its place, and the key action is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple two-parameter tool, the description covers the operation, parameter source, and return shape, which is especially important because there is no output schema. It does not describe failure cases or size limits, but the core calling contract is sufficiently clear.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already documents both parameters fully, so the baseline is 3. The description adds value by instructing that both values come from search_emails results and that both should be provided, clarifying provenance and intended usage beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states a specific action: fetch an email attachment, upload it to S3, and return a URL. It conveys the resource and outcome well, though it does not explicitly distinguish itself from sibling tools like read_attachment or list_attachments.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a clear usage context: 'Use this when you need to access a file attached to an email' and points to where the identifiers come from. It does not provide explicit exclusions or alternative tool guidance, so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_emailsGet EmailsA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
idsYesList of email IDs from search_emails results (max 20).
as_teammateNoRead a consented teammate's emails instead of your own — pass their email. Gated on that teammate's conversation-sharing setting; a teammate who hasn't shared is rejected. Pass the same email you used with search_emails.

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description usefully discloses the return shape (dict with count and emails array) and the 6000-character truncation, which annotations cannot convey. It doesn't detail failure behavior or consent-gated teammate rejection, but readOnlyHint covers side-effect safety and the schema already documents the consent rule.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and well-structured with summary and returns sections. It front-loads the core action and includes only essential workflow and return information, with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description provides the return contract (count and truncated emails array) instead of leaving the agent to guess. Combined with 100% schema parameter coverage and readOnlyHint, the tool is fully actionable.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so both ids and as_teammate are fully documented in the schema, including the max 20 limit and consent gating. The description's 'by ID / after search_emails' context adds only a high-level workflow cue, not deeper parameter semantics.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource ('Fetch full email content by ID') and clearly positions it as the follow-up to search_emails. This differentiates it from sibling search and attachment tools well enough for an agent to select it correctly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly says to use this tool after search_emails and when closer inspection is needed, which gives the agent a clear invocation path. This effectively communicates when this tool is appropriate versus relying on search results alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_integration_statusGet Integration StatusA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description discloses detailed behavioral nuances: the meaning of a missing key, that shared_calendars is not a connection-status entry, and the distinct slack flags (connected vs slack_mcp_connected). This goes far beyond what annotations provide and prevents misinterpretation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is structured with a summary and a returns section, each sentence serving a distinct purpose. It is compact yet comprehensive, with the most critical usage context front-loaded and the return details logically organized without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Since there is no output schema, the description fully covers the return format, edge cases, and ambiguous keys. It explains how to read results, what missing keys mean, and the special handling for slack and shared_calendars. An agent can rely on it alone to correctly interpret the response.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There are zero parameters, so the schema already covers everything. The description doesn't need to explain parameters and correctly focuses on the return structure. Baseline of 4 is appropriate because no parameter info is missing or needed.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool returns which integrations the account has connected, keyed by the same integration_id used in get_tool_connect_url. This specifies the resource and its scope, and it differentiates itself by mentioning the keying scheme and the sibling tool it complements.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit, actionable usage guidance: call this before telling a user an integration is unavailable, or before minting a connect link for one they say is already connected. This provides clear when-to-use context and indirectly sets expectations about when not to use it (i.e., when you already know the status).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_linkedin_monitorsGet Linkedin MonitorsA
Read-only
Inspect

Default (no monitor_id): one summary per monitor with members_count (the number of watched LinkedIn profile URLs — list mode only, 0 for network/topic) and members_sample (the first 5 URLs), not the full roster, since a list monitor can watch thousands. To read a monitor's full roster, pass its id (returned on each summary) as monitor_id; that returns the URL list paged by offset/limit with a truncated flag — re-call with offset += limit while truncated. The URLs are in the exact shape setup_linkedin_monitoring takes back. run_weekdays is weekday ints Mon=0 … Sun=6 in the user's timezone. last_run_at is null before the monitor's first run. By default, a dict {'monitors': [{'id', 'name', 'mode', 'is_active', 'members_count', 'members_sample', 'keywords', 'writing_instructions', 'comment_scope', 'draft_comments', 'fetch_engagers', 'engager_icp', 'engager_instructions', 'run_weekdays', 'recency_days', 'last_run_at'}, ...]} — an empty list if the agent has no monitors. With monitor_id: {'monitor_id', 'name', 'members_count', 'members' (this page's URL list), 'offset', 'limit', 'truncated'}. comment_scope is the user's own description of which posts are worth a comment (blank means the built-in criteria only). engager_icp filters who capture collects (blank means everyone) and engager_instructions is what to do with them (blank means take no action); both are blank when fetch_engagers is off, and both need re-passing when re-arming capture.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax roster URLs per drill-down page (default 200, capped at 200).
offsetNoRoster rows to skip when paging a monitor_id drill-down (default 0).
agent_idYesThe agent whose monitors to read.
monitor_idNoDrill into one monitor's watched-profile roster by its `id`. Omit for the per-monitor summary of all monitors on the agent.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description discloses substantial behavior: default summary vs. roster drill-down, pagination with offset/limit and truncated flag, the exact URL shape expected by setup_linkedin_monitoring, timezone semantics for run_weekdays, and last_run_at being null before first run. This goes well beyond what annotations alone provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every sentence earns its place: core purpose, editing caveat, default behavior, drill-down behavior, paging algorithm, field semantics, and return shapes. It is structured with summary and returns sections, making the volume navigable and front-loading the primary purpose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given a read-only tool with no output schema, the description fully compensates by specifying both return dict shapes, all key field meanings, pagination semantics, blank-value conventions for engager fields, and the empty-list case. The parameter schema and readOnlyHint annotations are also complete, so an agent has everything needed to invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already documents all four parameters with good descriptions (100% coverage), so the baseline is 3. The description adds extra meaning for monitor_id by explaining that its id comes from each summary row, and it clarifies paging behavior for offset/limit with the truncated flag. That enrichment justifies a 4.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb-resource pair: 'Read the LinkedIn post monitors configured on an agent', then enumerates exactly what is returned (mode, schedule, writing instructions, enabled actions). This clearly separates it from sibling tools like setup_linkedin_monitoring, edit_monitor_members, and query_monitored_posts, which have different purposes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit guidance on when to use the tool: read current config before editing a monitor because setup_linkedin_monitoring overwrites unpassed fields. It also explains when to use the default summary vs. the monitor_id drill-down. It does not explicitly list alternative read tools or when-not-to-use conditions, so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_linkedin_postsGet Linkedin PostsA
Read-only
Inspect

RECENCY WINDOW: only posts from the last posted_within_days days (default 7) are returned — this tool answers "what has this person posted lately", not "show me their post history". A profile with nothing in that window comes back with profiles[<key>]['status'] == 'no_recent_posts' and no posts for that profile — that is normal and does not mean the lookup failed. In that case, personalize off the prospect's role, company, or headline instead of forcing a stale post reference; do not widen posted_within_days just to find something to quote unless the user asked for older posts specifically.

Only the profile's own original posts count as "recent activity" — reshares and quote-posts are excluded.

ALWAYS BATCH: pass every profile URL in ONE call — batching is both cheaper and faster than one call per profile. Up to 1000 profiles per call; split a larger list across calls. Each returned post carries a profile_input field identifying which profile it came from (the matched input identifier).

COST: 0.02 credits per unique scrapeable profile searched, PLUS 0.5 credits for each profile that actually has a post in the window. If the user has fewer credits than profiles, only the affordable first profiles are looked up and the rest are reported in skipped_profiles_due_to_credits.

LARGE-BATCH COST GATE: because each profile can cost up to 0.52 credits, a call that would search more than 100 profiles is refused with a ModelRetry that states the exact credit cost, UNLESS large_batch_approved=True is passed. Set large_batch_approved=True ONLY after the user has seen the credit cost and agreed to it — in interactive chat, that means you told them the number and they said yes; in stored trigger code, ONLY if the user explicitly approved this recurring spend when the trigger was set up. Do not set it reflexively to silence the retry. A dict with the following keys.

  • posts: list of {url, text, author, posted_at, days_ago, profile_input, reactions, comments} — newest-first per profile. reactions/comments are engagement COUNTS, not the people who engaged — use fetch_post_engagers for the actual list of people.

  • total: number of posts returned.

  • profiles: dict keyed by the normalized profile identifier (the same value as each of that profile's post's profile_input), each {'status': 'ok'|'no_recent_posts'|'not_found', 'posts': int} — covers every attempted or cached profile. not_found means the actor could not resolve the target (renamed/private/deleted); no_recent_posts means it resolved but nothing fell in the window. (Distinct from the top-level unresolvable_profiles list below, which is inputs rejected at URL classification and never sent to the actor.)

  • posted_within_days, profiles_lookup_count, credits_charged, and optionally skipped_profiles_due_to_credits / warning / unresolvable_profiles.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax posts per profile (default 3, max 5)
linkedin_urlsYesList of PUBLIC profile URLs (linkedin.com/in/<slug>) or bare usernames, e.g. ["https://linkedin.com/in/johndoe", "janedoe"]. Sales Navigator URLs (linkedin.com/sales/lead/...) and company URLs are NOT scrapeable — they are skipped and reported in `unresolvable_profiles`, and are NOT charged. If you only have a Sales Navigator URL, resolve a public /in/ URL first (e.g. via Apollo) before calling this.
posted_within_daysNoOnly return posts published in the last N days (default 7).
large_batch_approvedNoConfirm a lookup above the cost-gate threshold. Only set True once the user has seen and approved the credit cost (see the LARGE-BATCH COST GATE note above).

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=true, so the description carries the behavioral burden and exceeds it. It discloses the recency window, response status semantics, exclusion of reshares and quote-posts, credit costs, the large-batch gate, and the skipped_profiles_due_to_credits behavior. This is far more than typical and gives the agent an accurate mental model.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long, but it is well-structured with clear sections and every major paragraph serves a functional purpose: usage, recency semantics, batching, cost, and return shape. It is denser than strictly necessary, especially around cost and the large-batch gate, but the structure and front-loading keep it navigable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, but the description fully compensates by documenting the return keys, status values, and engagement-count semantics. It also covers failure modes, credit limits, approval policy, batching constraints, and profile URL input rules. An agent has everything needed to call this tool correctly and interpret its results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3, but the description adds meaningful semantic context beyond the schema. It explains the credit cost formula, when large_batch_approved may be set, how profile_input maps responses to requested URLs, and why unresolvable_profiles are not charged. Some details like limit defaults are only in the schema, but the added context justifies a score above baseline.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: fetch recent LinkedIn posts from one or more profiles using Apify. It further distinguishes this tool from post-history tools and from fetch_post_engagers by emphasizing the recency window and noting that engagement counts are not the people who engaged. This makes the tool easy to separate from siblings even without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly states when to use the tool: to check what someone posted recently and to personalize outreach off something they actually said. It gives clear when-not guidance by saying this is not for post history, by warning against widening posted_within_days unless asked, and by naming fetch_post_engagers as the alternative for the actual list of engagers. The ALWAYS BATCH instruction also gives concrete operational guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_luma_eventsGet Luma EventsA
Read-only
Inspect

Fetches every page (no truncation), so the returned list is the complete calendar — upcoming and past events alike. Use it to answer "which event is next", to find an event's id for get_luma_guests, or when a luma_event_created trigger fires and you need the full event context. Dict with events array and count. Each event dict is Luma's event object, the useful fields being: id (starts with "evt-", the key get_luma_guests takes), name, start_at / end_at (ISO 8601 UTC), timezone (IANA), url (the lu.ma page), location_type, meeting_url, visibility, registration_open, require_approval, spots_remaining, description_md.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, and the description builds on that by disclosing no-truncation pagination, inclusion of both upcoming and past events, and the exact return shape (dict with events array and count). It adds useful behavioral context such as ISO 8601 UTC timestamps and id prefix evt- that annotations do not provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core action, then organizes behavioral details and return fields into clear summary and returns sections. Every sentence adds value, and the enumerated useful fields prevent ambiguity without bloating the description.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter read-only tool with no output schema, the description is fully self-sufficient. It covers scope, ordering, completeness, return structure, and relevant field formats, and it links to the sibling tool that consumes the output. Nothing essential is missing for correct invocation or interpretation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the schema is trivially complete and no parameter documentation is needed. The description focuses on return-value semantics instead, which is appropriate for a no-input read tool.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'List all events on the user's Luma calendar, most recent first.' It further distinguishes itself by noting it fetches every page with no truncation, making it a complete-calendar listing rather than a filtered search. It also references get_luma_guests as the consumer of the returned event id, clarifying its role among siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives concrete usage contexts: answering 'which event is next', finding an event's id for get_luma_guests, and handling luma_event_created trigger fires. It does not explicitly state when not to use it or name exclusion alternatives, but the guidance is clear enough for an agent to select it appropriately.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_luma_guestsGet Luma GuestsA
Read-only
Inspect

Paginates to exhaustion — the returned list is the complete guest roster, every approval status included (approved, pending approval, waitlisted, invited, declined). Deciding "who hasn't registered yet" from a partial roster produces false negatives that re-nag people who already signed up, so there is deliberately no page or field trimming here.

When this runs in an agent, each guest is also saved and linked to the agent's Output tab as an agent_search_results person row (keyed by their LinkedIn identifier when the form captured one, else their email), so the roster persists without a separate record step. Best-effort: a persist failure never fails the read. Re-running updates rows in place; a guest the user has removed from the list stays removed. Dict with event_id, guests array, count, and (when saved in an agent) a list summary {list_name, created, updated, total}. Each guest dict is Luma's guest object, the useful fields being: user_email, user_name, approval_status, registered_at, invited_at (null when the guest found the event themselves), registration_answers (the event form's question/answer pairs — a LinkedIn-URL question shows up here), utm_source (which outreach channel drove the registration, when the invite link carried ?utm_source=).

ParametersJSON Schema
NameRequiredDescriptionDefault
event_idYesThe Luma event id from get_luma_events (starts with "evt-").
list_nameNoShort kebab slug naming the Output-tab list bucket. Absent, guests land in the 'default' list.

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Despite readOnlyHint=true in annotations, the description discloses meaningful additional behavior: it persists each guest to the agent's Output tab, records rows keyed by LinkedIn identifier or email, updates in place, and keeps removed guests removed. It also states persist failures never fail the read. This goes well beyond the annotation and gives the agent an accurate mental model of side effects. No contradiction with readOnlyHint exists because the external Luma data is never mutated.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every sentence carries load: the summary front-loads the exhaustive-roster guarantee, the middle paragraph justifies the design and reveals side effects, and the returns section is organized. There is no filler or repeated schema data. The structure with summary and returns sections makes it easy to scan.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description fully documents the return shape: event_id, guests array, count, list summary, and the useful fields inside each guest object including approval_status, invited_at, registration_answers, and utm_source. It also covers persistence behavior, failure semantics, and re-run behavior, so an agent has everything needed to invoke the tool and interpret results correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the structured schema already fully documents event_id (including the "evt-" prefix) and list_name (including default behavior). The description adds no extra parameter-level meaning beyond the schema; it only mentions how the saved person rows are keyed. This meets the baseline but does not exceed it.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb-resource pair: "List every guest of a Luma event, with full registration detail." It also immediately clarifies the tool's exhaustive scope (all approval statuses, paginates to exhaustion), which distinguishes it from event-listing tools like get_luma_events. This is unambiguous and actionable.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides strong usage context: it warns that inferring "who hasn't registered yet" from a partial roster causes false negatives and re-nagging, and explains why the tool deliberately avoids trimming. This tells the agent when to rely on the complete output. It stops short of explicitly naming alternatives or saying "use X instead when Y," so it loses the top point for missing explicit exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_mailbox_deliverabilityGet Mailbox DeliverabilityA
Read-only
Inspect

Call this when the user asks why their emails land in spam or the junk folder, or before launching a campaign to confirm the sending mailbox is configured to deliver. Each mailbox reflects the last DNS check — Sliq re-checks daily and on connect, so the data is current without asking the user to re-run it.

verdict is the worst of the four record statuses: 'fail' (a record is misconfigured and may block or junk sends, so is_send_ready is False), 'warn' (advisory — delivery still works), or 'pass' (all four clean). score is a 0-100 roll-up that score_tier buckets into 'good' / 'needs_work' / 'critical'. Each checks entry carries the per-record diagnostic (what is wrong) and fix_instruction_markdown (how to fix it, null when that record passes) — relay these when the user wants to fix a failing record. A mailbox's deliverability is None when it has never been checked — tell the user to open that mailbox in Settings and run the DNS check. An empty mailboxes list means no email account is connected. { 'mailboxes': [ { 'email': str, 'provider': str, 'is_default': bool, 'deliverability': { 'domain': str, 'verdict': 'pass' | 'warn' | 'fail', 'score': int, 'score_tier': 'good' | 'needs_work' | 'critical', 'is_send_ready': bool, 'last_checked_at': str, # ISO 8601 'dns_provider': str, 'checks': [ {'record_type': 'spf' | 'dkim' | 'dmarc' | 'mx', 'status': 'pass' | 'warn' | 'fail', 'raw_record': str, 'diagnostic': str, 'fix_instruction_markdown': str | None}, ... ], }, }, ... ], }

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description explains data freshness (daily/on-connect recheck), the meaning of verdict and score_tier, the worst-of-four logic, null deliverability for never-checked mailboxes, and empty-list semantics. This is rich behavioral disclosure that helps the agent interpret results correctly.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but well-structured: a concise summary followed by a return-value breakdown. Every section contributes meaningful information, especially since there is no formal output schema. It is somewhat verbose but earns most of its length.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a no-parameter, read-only tool, the description covers the core purpose, when to use it, freshness behavior, edge cases (null deliverability, empty mailboxes), and full return semantics including diagnostics and fix instructions. Nothing an agent needs to interpret the result correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the baseline is 4. There are no parameter semantics to explain, and the schema is complete for an empty input object. The description does not need to add anything here.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Read the DNS deliverability verdict (SPF, DKIM, DMARC, MX) for each connected mailbox.' It is immediately clear what the tool does and is distinct from generic get_* tools, with concrete coverage of the four record types.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit usage context: 'Call this when the user asks why their emails land in spam or the junk folder, or before launching a campaign to confirm the sending mailbox is configured to deliver.' It does not name specific alternative tools or provide when-not-to-use exclusions, so it falls just short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_mcp_change_callsGet Mcp Change CallsA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax calls to return (default 100, max 200).
offsetNoCalls to skip from the start of the burst (default 0). When `has_more` is True, re-call with offset += limit.
agent_idYesID of the agent the change was made to.
change_idYesThe change's id: the `id` of a `get_agent_details` `mcp_changes` entry, also shown as `(change N)` in the agent's Recent Activity.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=true, so the description adds real value: oldest-first ordering, the grouping behavior that hides prior calls, and pagination via has_more/offset. It stops short of stating auth requirements or retention limits, but the behavioral picture is solid.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Structured with summary and returns sections, front-loading the core action before the caveat about missing arguments. Every sentence carries information; no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description compensates by describing the return dict fully (calls with id/created_at/arguments, plus has_more) and pairs it with offset-based pagination guidance. Nothing needed to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so limit, offset, agent_id, and change_id are already fully documented. The description supplies the conceptual meaning of a 'change' but no additional parameter syntax or format detail beyond the schema, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Read every call in one change') and explains the domain concept that distinguishes it: a change entry groups a burst of calls but only carries the latest call's arguments. An agent can tell exactly what this returns without ambiguity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly scopes usage: 'use this when you need the others, e.g. every prospect an update_prospect burst edited.' The triggering condition is clear, but no alternative tool (e.g. get_agent_details for the summary entry) is named as the branch point.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_meeting_transcriptGet Meeting TranscriptA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
meeting_idYesThe `id` of the meeting (from `search_meetings` results).

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already mark the tool as readOnly, and the description adds valuable non-obvious behavior: the transcript is full while the summary is truncated to 6000 chars, the exact return fields are listed, and no-match returns `{'meeting': None}`. This goes beyond what the schema alone conveys.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured: a summary block front-loads the core purpose and usage trigger, and a returns block efficiently documents the output shape. The sales-call example is concrete and earns its place without bloating the text.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter, readOnly tool with no output schema, the description is complete: it explains when to call it, what input to provide, exactly what the returned dict contains, and the no-match behavior. An agent has everything needed to invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema fully documents `meeting_id` as the id from `search_meetings` results, and description coverage is 100%. The description's mention of the meeting id appearing in the return adds minimal extra meaning beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action ('Fetch the full, untruncated transcript for a single meeting') and clearly differentiates the tool from `search_meetings`, which returns only a truncated preview. This makes the tool's role unambiguous even among many sibling get_* tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says to use it after `search_meetings` when the 10000-char transcript preview was cut off and full spoken words are needed, with a concrete sales-call example. It does not mention when not to use it or name alternative transcript tools like `get_run_transcript`, so it stops short of a full exclusion set.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_message_experimentsGet Message ExperimentsA
Read-only
Inspect

Each test carries versions A and B with their template text, sent / replied / interested / meetings counts, and a verdict: too_early until each version has 30 sends, then the leader with its probability of scoring higher on the test's metric — leading, or wins at 95%. A send step's metric is replied. A connection request's is accepted, with an accepted count per version; there an empty version text is a request sent with no note, and recent requests haven't all been answered yet, so the version accepted faster reads a little ahead early on. Relay that probability plainly, and say "too early" when it is; at current volumes a test takes weeks. "Replied" counts any reply on that channel after the send, including replies to later steps. The tests (experiments), newest first, as {experiments: [{node_id, node_label, metric, started_at, ended_at (null while running), ended_by, versions: [{label, template_id, text, sent, replied, interested, meetings, accepted (connection requests only)}], verdict}]}, where ended_by says how a past test ended — keep_a / keep_b when that version went on alone, edit_a / edit_b when that version was edited (which restarts the test), edit otherwise — and is null while it runs; across every agent each also carries agent_id, agent_title and owner_email. The experiment at experiment_index also carries each version's messages (newest first: {prospect_id, prospect_name, person, sent_at, text, outcome}), filtered to outcome and paged 50 at a time from offset, with messages_total. A past test that sent nothing is left out, so an empty experiments means no test in that scope is running or ever sent a message.

ParametersJSON Schema
NameRequiredDescriptionDefault
offsetNoWhere the page of messages starts.
node_idNoA send or connection-request step's node id, to scope to that step. Needs `agent_id`.
outcomeNoOnly messages whose prospect reached this outcome (meeting counts as interested and replied; interested counts as replied; on a connection request, replied counts as accepted). None = every sent message.
agent_idNoID of the agent (campaign) to scope to. Omit for every agent.
as_teammateNoRead a consented teammate's tests instead of your own — pass their email. Gated on that teammate's conversation-sharing setting. Omit for your own.
experiment_indexNoWhich experiment's messages to include, 0 = newest.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond readOnlyHint=true: explains how A/B tests run, the verdict lifecycle (too_early until 30 sends, leading, wins at 95%), per-metric definitions (replied vs accepted), the caveat that recent connection requests skew acceptance, and the weeks-long timeframe. This is unusually rich behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the summary, then scope, then verdict semantics. Dense but each sentence carries unique information; the returns block is long yet justified by the absence of an output schema. Minor verbosity in the metric/caveat discussion.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema and 6 parameters, the description supplies the full return shape, the ended_by vocabulary, the per-experiment messages paging, and the empty-result meaning. An agent has everything needed to call and interpret it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3, but the description adds holistic meaning: how agent_id/node_id combine to set scope, that experiment_index selects which experiment's messages return, and that outcome filters messages (not the top-level list). Slightly above baseline.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource (read A/B tests of flow-step messages and connection-request notes), defines what a 'test' is operationally, and gives recognizable question phrasings. It is clearly distinct from siblings like get_campaign_flow or get_node_history.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly describes the three scoping modes (one step via agent_id+node_id, one agent, or every agent) and the questions the tool answers. It does not name a sibling alternative to route away from, so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_message_tag_ratesGet Message Tag RatesA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
channelNo'linkedin' or 'email' to bound the population; omit for both.
measureNo'accepted', 'replied', 'interested', or 'meeting'.replied
sendersNoIn a shared workspace, the teammate email addresses whose messages to score. Omit to score every sender in the workspace. Any email not in the workspace is dropped; if that leaves no valid teammate, the result is empty — it does NOT fall back to the whole workspace.
group_idYesThe tag group to score (from list_message_tag_groups).
agent_idsNoScope to one or more campaigns (agent_tasks ids from a group's `campaigns`); omit for all campaigns. Campaign membership is the prospect's current one.
positionsNoThe conversation positions to score — 'first' (each conversation's first message), 'follow_up' (later messages sent before the prospect replied), and/or 'reply' (messages sent after the prospect replied); omit for all.

TDQS

A4.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only provide readOnlyHint: true, which the description does not contradict. The description goes beyond by detailing how rates are computed (conversation-level, one data point per prospect, scoring logic per measure), including edge cases like messages sent after the outcome not counting, and the synthetic 'No match' row behavior. This transparency is valuable for understanding the tool's semantics without needing to run it.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but well-structured: a focused summary paragraph, then a separate <returns> block for the output format. The summary front-loads the purpose and key constraints, avoiding redundancy. Every sentence adds specific guidance, so the length is justified by the tool's complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (six parameters, nuanced scoring logic, and synthetic rows in output), the description fully covers what an agent needs to call it correctly. It explains measure-specific behavior, parameter interactions, and return format, including the `signal` field. The only gap is lack of auth or rate limit details, but those are not mentioned in annotations and are not critical for this read-only tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although the schema already describes each parameter (coverage 100%), the description adds critical context: for `measure` it explains the scoring differences (e.g., `accepted` uses connection-request notes, others use reply-capable messages). For `positions` it explains the no-op for `accepted` and the empty `replied` behavior. For `agent_ids` it clarifies that campaign membership is the prospect's current one. These enrich the schema beyond simple field definitions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description begins with a clear, specific statement: 'Per-tag outcome rate for one measure over a group's classified messages' and provides concrete examples ('which opening sentence gets the most replies'). It clearly distinguishes this tool from a sibling like get_message_tag_cross_tab by focusing on single-measure rates per tag, and from get_segment_rates by focusing on message tags versus segments.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit when-to-use guidance with examples and notes that `agent_ids` scopes to campaigns, referencing list_message_tag_groups for obtaining campaign IDs. It also clarifies when NOT to use certain parameters, such as `positions` being a no-op for `accepted` and that a positions filter of only 'reply' makes `replied` always empty. It also links to query_tagged_messages for filtering untagged messages.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_message_tag_unclassified_countGet Message Tag Unclassified CountA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
group_idYesThe tag group to count for (from list_message_tag_groups).

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description explains important semantics: it counts messages with no classification row, excludes sentinel 'No match' rows, pairs with a related count, and only counts the current shared agent. This gives the agent a precise model of what the tool will and will not include.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary is dense but every sentence earns its place: it defines the count, explains its relationship to related tools, handles the shared-agent edge case, and disambiguates from sentinel rows. The separate `<returns>` section cleanly states the output shape without cluttering the summary.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter read-only count tool with no output schema, the description is complete. It explains what is counted, what is excluded, how it relates to sibling operations, the shared-agent behavior, and the exact return dictionary shape.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already fully documents `group_id` with type, requirement, and provenance from `list_message_tag_groups`. The description adds context about the group's classify scope, but does not need to add more since schema coverage is 100% and the single parameter is straightforward.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states a specific verb and resource: counting sent messages in a tag group's classify scope that have no classification row yet. It distinguishes this count from `classified_messages` and from sentinel 'No match' rows, making the tool's unique purpose unmistakable even among many siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives strong contextual guidance by explaining this count pairs with `classified_messages` and is exactly the population a `classify_message_tag_group` run with force off would process. It also notes the shared-agent scoping behavior. It does not explicitly name an alternative to avoid, but the intended use case is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_node_historyGet Node HistoryA
Read-only
Inspect

Reads the append-only position log directly, so an off_sequence node id a later flow edit dropped still returns its history. A node no one has moved off — including the start node, which stores no position rows of its own — comes back empty. Dict with node_id, count, and prospects array. count is how many prospects went through and moved on in total (independent of limit/offset, so the page count is ceil(count / limit)); prospects is the requested page, newest visit first, each {id, name, email, linkedin_url, provider_id, current_node_id, visited_at} — current_node_id is the node they rest on now, visited_at the ISO time they last held node_id.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax prospects returned per page, newest visit first (default 50, capped at 200).
offsetNoHow many of the newest-first prospects to skip. Page through a node with more than `limit` history by re-calling with offset 0, then `limit`, then `2*limit`, … until `offset >= count`.
node_idYesThe node whose history to read — a node id from get_campaign_flow.
agent_idYesID of the agent (campaign) to read.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description discloses behavioral details: it reads the append-only position log directly, still returns history for dropped off_sequence nodes, and returns empty for nodes no one has moved off, including the start node. It also explains count semantics independent of limit/offset, which is valuable non-obvious behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is longer than minimal but well-structured with a summary and returns section, and it front-loads the core purpose before edge cases. It includes some expansion such as the node-detail panel comparison but that aids orientation, so it earns a high score rather than a perfect one.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the absence of an output schema, the description compensates fully by documenting the return shape, pagination behavior, edge cases, and sibling distinctions. An agent has everything it needs to invoke the tool correctly and interpret the result.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real semantic value: it clarifies that agent_id can be any user campaign, that node_id comes from get_campaign_flow, and explains how limit/offset interact with the total count for pagination. These details go beyond the schema's property descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: it reads a Campaign Flow node's 'went through' history, defines exactly which prospects are included, and clarifies this is the history counterpart to get_campaign_flow. It also explicitly differentiates from query_prospects for current occupancy, so an agent can select it without confusion.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives concrete use cases ('who passed through the connect step', 'who has already been through step 2'), names the alternative for current occupants ('filter query_prospects on current_node_id instead'), and notes it works across any of the user's campaigns, not just the agent in context. This is explicit when-to-use and when-not-to-use guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_outreach_approvalGet Outreach ApprovalA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
agent_idNoThe agent whose effective settings to read. When omitted, reads the current agent's settings if this chat is tied to one, otherwise the account-wide settings.

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already provide readOnlyHint=true, and the description adds useful non-obvious context: pending_approval is a normal state produced by these settings, not a failure, and it explains override vs account-wide resolution. It stops short of disclosing other behavioral details like permissions or performance, but the read-only annotation plus this context covers the main surface.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary is front-loaded with the core purpose, followed by a practical diagnostic cue and clear optional-parameter guidance, and the returns section is compact and useful. No sentences are wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given one optional parameter, a readOnlyHint annotation, and no output schema, the description is complete: it explains scope resolution, return structure, value semantics, and the practical meaning of the settings. An agent has everything needed to call it correctly and interpret the result.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already documents `agent_id` at 100% coverage, and the description's parameter guidance mostly restates the schema's meaning (specific agent vs account-wide vs current agent). Since the schema carries the semantic load, the description adds no substantial new parameter insight beyond the baseline.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies a specific verb and resource: reading per-subtype outreach approval settings, and it differentiates the read semantics from the sibling setter by noting the returned settings are the gating that holds messages in pending_approval. It does not explicitly name `set_outreach_approval` or another alternative, so it falls just short of full sibling differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a clear when-to-use: read this before diagnosing a pending_approval message as stuck, and it explains the optional `agent_id` decision. It does not explicitly state when not to use it or direct to an alternative setter, so not a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_outreach_windowGet Outreach WindowA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=true, so the description carries meaningful additional context. It explains that Sliq only sends within this window, that a closed or paused window is not a failure, and that sends cannot be moved into a closed window. This is exactly the behavioral nuance an agent needs and is not present in the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is structured and efficient, with the core purpose front-loaded and the diagnostic guidance placed immediately after. Every sentence earns its place, and the return details are justified because no output schema exists.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter read-only tool, the description is complete: it covers what the tool returns, when to use it, how to interpret edge cases such as closed windows and empty active_days, and the timezone legacy default. No necessary context is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There are zero parameters, so the baseline is 4 and the schema fully covers inputs. The description instead details the return fields, which is more valuable here given there is no output schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States it retrieves the account's outreach activity window and whether it is open right now, a specific verb and resource. The resource is clearly distinct from sibling getters such as get_outreach_approval and get_campaign_flow. No ambiguity remains even before reviewing structured fields.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says when to use it: check this when a queue looks idle before telling the user something is broken, because a closed window or empty active_days is a normal hold. It does not name alternatives, but the zero-parameter signature and unique resource make that omission minor.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_pending_approvalsGet Pending ApprovalsA
Read-only
Inspect

Covers four channels, each pending row bucketed by action-type category exactly as the page groups them: LinkedIn connection requests / messages / follows / post engagement, emails, Reddit comments, and manual actions. Same-post comment+reaction rows collapse into one engagement item, matching the page. Sequence-produced items (LinkedIn, email, and a manual step queued by a manual flow node) carry node_id/node_label naming the campaign-flow node that authored them — null for items created outside a sequence and for Reddit items. Cross-agent for the owner; a shared operator sees only the shared agent.

Bounded by default so an approval-gated campaign holding thousands of drafts doesn't return every full message. The default gives per-bucket/per-agent counts plus a short preview per bucket (first few items); pass bucket to page that one bucket's full item list. A bucket's preview_truncated (or count exceeding the preview) tells you to drill in. By default (no bucket), a dict {'pending': {'total': int, 'buckets': [{'bucket', 'title', 'count', 'preview_truncated': bool, 'agents': [{'agent_id', 'agent_title', 'count'}], 'preview': [item, ...]}]}, 'recently_resolved': {'window_days': int, 'total': int, 'items': [...]}, 'recently_shelved': {'window_days': int, 'total': int, 'items': [...]}}. The preview holds the bucket's first few full items. Recently-resolved lists only rows with a transition timestamp — manual done/dismissed, LinkedIn/email sent, Reddit submitted; cancelled or rejected drafts don't appear there. Recently-shelved lists rows the shelf-life sweep shelved (an unstarred draft that aged past its approval window); each item's target_provider_id is the prospect handle for person-addressed LinkedIn drafts (connection_request / message / inmail / follow) so a swept draft matches back to a prospect — blank for comment/reaction (post-addressed) and for email/reddit. Drill-down (bucket set): {'bucket', 'title', 'count', 'offset', 'limit', 'truncated': bool, 'items': [item, ...]} — full-text items, newest-first; count is the whole bucket, truncated means more beyond this page (re-call with offset += limit).

ParametersJSON Schema
NameRequiredDescriptionDefault
bucketNoDrill into one bucket for its full-text items, paged. One of 'email', 'linkedin_invite', 'linkedin_message', 'linkedin_follow', 'engagement', 'reddit', 'manual'. Omit for the bounded counts+preview.
offsetNoRows to skip in drill-down mode (default 0); re-call with `offset += limit` while `truncated` is true. Ignored without `bucket`.
as_teammateNoRead a consented teammate's pending approvals instead of your own — pass their email. Gated on that teammate's act-on-behalf setting (stricter than conversation sharing); a teammate who hasn't granted it is rejected. Omit for your own.
resolved_within_daysNoRecency window for the recently-resolved section (default 7). Only used in the default (no-`bucket`) mode.

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only provide readOnlyHint: true, so the description carries most of the behavioral burden. It clearly discloses the tool is read-only (lists, doesn't modify), explains the bounded default to avoid returning thousands of items, describes the recency windows for resolved and shelved rows, and notes cross-agent behavior (shared operator sees only shared agent). This is rich context beyond the annotation's basic hint, but does not mention any external side effects beyond read-only nature, so a 4 is warranted (would be 5 if it explicitly stated no side effects, but readOnlyHint covers that).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but information-dense, with a summary section followed by a detailed returns section. It front-loads the core purpose and use cases. Every sentence adds new information (channels, bucketing, node_id, pagination). The length is justified by the tool's complexity, but it could be slightly more compact without losing key details.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity of the output (nested dict, buckets, previews) and the fact there is no output schema, the description thoroughly explains the return structure, including the difference between default mode and drill-down mode, the meaning of `preview_truncated`, and the semantics of `target_provider_id`. It also covers edge cases like cancelled drafts not appearing in recently_resolved. An agent can fully understand how to call and interpret results without additional info.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents all parameters. However, the description adds significant context: describes when to use `bucket` for drill-down, that `offset` is ignored without `bucket`, and that `as_teammate` requires consent. It also explains the default recency window for `resolved_within_days`. This adds value beyond the schema descriptions, which are brief. Since coverage is high, baseline is 3, but the extra context justifies a 4.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description explicitly states it lists pending approvals, the same rollup as the home page, and covers four channels. It clearly distinguishes from other tools by focusing on what's waiting on the user, and even mentions related tools like request_user_action and get_outreach_approval implicitly. The verb (list) and resource (pending approvals) are specific.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit use cases: 'what's waiting on me?', checking if a manual action was handled, whether a draft was shelved. It also explains when to pass `bucket` for drill-down versus using the default preview. It implicitly contrasts with alternative tools like get_outreach_approval or manage_inbox, noting this is the rollup view.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_personalization_guideGet Personalization GuideA
Read-only
Inspect

Return the user's personalization guide — how they want a person researched before you write to them. Call this only when you are going to personalize a message; follow the returned guide. When none is set, returns a note and you should fall back to the default research rules in the outreach craft skill.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The readOnlyHint annotation already covers the safety profile. The description adds valuable behavioral context: an unset guide returns a note, the agent should fall back to default research rules, and the returned guide should be followed. It does not detail the output structure, but with no output schema present it still conveys the key return states.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, each earning its place: what the guide is, when to call the tool, and what to do when no guide exists. The trigger condition is front-loaded near the start. There is no redundant restatement of the title or annotations.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter retrieval tool, the description fully covers invocation trigger, the returned guide, the absent-guide case, and the fallback behavior. There is no missing information that would prevent an agent from calling it correctly. The lack of an output schema is acceptable because the description captures the two possible return states.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters and the schema already makes this explicit with an empty properties object and 100% coverage. No parameter documentation is therefore necessary. The description appropriately focuses on invocation context rather than parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states it returns the user's personalization guide and defines its content: how they want a person researched before writing. This verb-resource pair is distinct from sibling get_* tools like get_skill_guide and get_writing_style_guide. The phrase 'before you write to them' reinforces the tool's unique role in message personalization.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly says to call this only when personalizing a message, establishing a clear trigger condition. It also documents the fallback behavior when no guide is set, referencing the outreach craft skill as the alternative path. This leaves no ambiguity about when and how to use the tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_run_transcriptGet Run TranscriptA
Read-only
Inspect

Use this when a run's one-line summary isn't enough (e.g. "why did you skip person X in that run?"). Get a run's id from get_agent_details — each entry in its recent_runs list carries one; this returns the same message parts the web Activity tab renders for that run.

The transcript is paginated over its message parts — a heavy run records many. Page with offset while has_more is true. Dict with run_id, the run's summary message + details, status ('completed'/'failed'), created_at, total_messages (parts in the whole run), returned_messages (parts in this page), offset, has_more, and a messages array (role, content or tool_name/args, created_at) sliced to [offset, offset+limit). Empty messages and total_messages 0 when the run recorded no audit session (e.g. it failed before the agent started).

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax message parts to return (default 100, max 200).
offsetNoNumber of parts to skip from the start of the run (default 0).
run_idYesAn agent-run id — the `id` of a `get_agent_details` `recent_runs` entry (also shown as `(run N)` in the agent's Recent Activity feed).

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description goes well beyond the readOnlyHint annotation by disclosing pagination behavior, the meaning of has_more, and the edge case where a failed run records no audit session (empty messages, total_messages 0). This gives the agent realistic expectations about incomplete or empty results.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well structured with a summary paragraph, a usage paragraph, and a returns section. Every sentence earns its place, and the most important purpose is front-loaded before pagination details. No filler or repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite having no output schema, the description's returns block fully explains the response shape, including run_id, status, pagination fields, and the structure of the messages array. It also covers the empty-result edge case. An agent has everything needed to call and interpret this tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already documents all three parameters at 100% coverage with defaults and max values. The description adds useful complementary semantics by explaining the pagination loop ('Page with offset while has_more is true') and clarifying how offset slices the messages array, which is value beyond the raw schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Read the transcript of one past background agent run — every tool call and reasoning step the agent recorded during that run.' This clearly identifies both the action and the scope, and distinguishes it from summary-level tools like get_agent_details.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says when to use it: 'Use this when a run's one-line summary isn't enough' with a concrete example. It also tells the agent where to get the required run_id (from get_agent_details recent_runs). It does not state explicit exclusions or compare against all alternatives, so it stops just short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_segment_funnelGet Segment FunnelA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
agent_idYesThe campaign (agent) whose funnel to split.
group_idNoThe segment group to split by, from `groups`. Omit for the first group that tags this campaign.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With annotations only declaring readOnlyHint, the description carries real behavioral weight: each person is counted once (so rates differ from per-channel tools), funnel.reached counts everyone who got at least that far while other stage keys count only people currently at that stage, auto-tagging of new people within ~15 minutes, plus the not_tagged/out_of_credits/'No match' semantics and the thin/early/solid signal thresholds.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is long but front-loaded: the summary sentence comes first, followed by the divergence warning, then quoting guidance, then returns. Every block earns its place against the toolbar's complex nested output, though the embedded returns section is denser than it needs to be.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, and the description compensates fully: it enumerates the return keys (groups, selected, credits_per_person), the funnel shape, the per-tag entries carried by everyone, signal thresholds, and the null-tag fallback entry. Nothing an agent needs to interpret or call the tool is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so both parameters are already documented, including the omit-for-first-covering-group default. The description corroborates this ('Omit for the first group that tags this campaign') and ties group_id to the `groups` output, but adds no new syntax or format meaning beyond the schema, which is the baseline-3 case.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The summary states a specific verb+resource: one campaign's lead funnel split by a segment group's tags, mirroring the campaign Summary view filtered by tag. It explicitly names and distinguishes itself from get_segment_rates ('which scores each channel separately'), so an agent can tell the two apart without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives concrete motivating questions ('how are VPs doing on this campaign', 'which seniority replies best here'), states the alternative and when it applies (get_segment_rates when channel-level scoring is needed), and routes the drilling-down case to query_task_people with the exact filter set to use.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_segment_ratesGet Segment RatesA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
channelNo'linkedin' or 'email' to bound the population; omit for both.
measureNo'accepted', 'replied', 'interested', or 'meeting'.replied
sendersNoIn a shared workspace, the teammate emails whose prospects to score (a group's `senders`). Omit to score every teammate. Any email not in the workspace is dropped; if that leaves no valid teammate, the result is empty — it does NOT fall back to the whole workspace.
group_idYesThe segment group to score (from list_segment_groups).
agent_idsNoScope to one or more campaigns (agent_tasks ids from a group's `campaigns`); omit for all campaigns.

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare readOnlyHint=true, and the description adds meaningful behavioral context beyond that: it explains the scoring unit (one data point per prospect-channel), how multi-team-member touches are handled (one data point per prospect), the synthetic 'No match' row behavior, and the confidence thresholds for `signal`. This is rich behavioral disclosure that helps an agent predict side effects and output shape.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with a summary and returns section, and it front-loads the core purpose. It is slightly dense but every sentence adds value—scoring semantics, edge cases, and related tools are all covered without fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only analytics tool with no output schema, the description covers the return structure, edge cases (untagged people, invalid senders), and confidence interpretation. It doesn't explicitly describe pagination or error behavior, but the provided context is sufficient for an agent to call it correctly and interpret results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all five parameters. The description adds some context (e.g., `agent_ids` maps to a group's `campaigns` list, `senders` scopes to teammate emails), but most parameter meaning is already in the schema. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('get') and resource ('per-tag outreach outcome rate for one measure over a segment group's classified people'), and gives concrete examples ('which seniority replies most', 'which role function books the most meetings'). It clearly distinguishes itself from sibling tools like get_message_tag_rates and query_segment_people by focusing on segment-group-level aggregate rates.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains when to use it (to see per-tag outcome rates for a segment group) and provides routing hints: pass `agent_ids` to scope to campaigns, and use `query_segment_people(untagged=true)` to list people classified as nothing. It doesn't explicitly name alternatives or exclusions, but the context is clear enough for an agent to select it over siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_segment_unclassified_countGet Segment Unclassified CountA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
group_idYesThe segment group to count for (from list_segment_groups).

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already provide readOnlyHint=true, and the description adds meaningful behavioral nuance: it counts only the current shared agent when operating a shared agent, and it clarifies that 'No match' people are already in classified_people. This goes beyond the annotation without contradicting it.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with a <summary> and <returns> section, front-loading the core meaning. Every sentence adds value—scope, relationship to classified_people, shared-agent behavior, and the 'No match' distinction—without redundancy or fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter read-only tool, the description is complete: it explains the counting scope, clarifies edge cases, names related tools, and explicitly describes the return dictionary {group_id, unclassified_people}. Since no output schema exists, the return description fills that gap adequately.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the single required parameter group_id is already described as 'The segment group to count for (from list_segment_groups).' The description adds the concept of 'classify scope' and mentions the return fields, but it does not materially enrich the parameter semantics beyond what the schema provides, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action ('get') and resource ('segment unclassified count') and defines exactly what is counted: people in the group's classify scope with no classification row yet. It also distinguishes the concept from 'No match' people and links it to the 'classified_people' count from list_segment_groups, so an agent can clearly understand the tool's unique purpose.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context for when this tool is relevant: it pairs with the classified count from list_segment_groups and represents the population that a classify_segment_group run with force off would process. It does not explicitly say 'use this instead of X' but the relationship to sibling tools is sufficiently clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_skill_guideGet Skill GuideA
Read-only
Inspect

Return the best-practice guide for a Sliq workflow — read and follow it before acting on that workflow. skill selects the guide.

ParametersJSON Schema
NameRequiredDescriptionDefault
skillYes

TDQS

A3.9/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already mark readOnlyHint=true, so the description doesn't need to re-establish safety; it adds the useful context that the returned guide should be followed. It does not disclose output format or any further behavioral constraints, but none are required for this simple getter.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is one efficient sentence with a front-loaded action, followed by the usage instruction and a parameter pointer. No redundant words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter, read-only tool with no output schema, the description covers what it returns and when to use it. It could explicitly state the return format (e.g., text/markdown), but 'guide' is sufficient for an agent to know what to expect.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% and the only parameter guidance is '`skill` selects the guide,' which mostly restates the parameter name. The description does not compensate for the enum's lack of descriptions, so an agent must infer the meaning of each skill value from the enum labels themselves.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb ('Return') and resource ('best-practice guide for a Sliq workflow'), and the added 'read and follow it before acting' frames the tool's role. This distinguishes it from sibling getters like get_personalization_guide and get_writing_style_guide by scoping it to Sliq workflows generally.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives an explicit temporal usage cue: read and follow the guide before acting on the workflow. It does not name alternatives or exclusion cases, but the context is clear enough for a single-purpose getter.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_social_listening_configGet Social Listening ConfigA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
agent_idYesThe agent whose social-listening config to read.

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations provide readOnlyHint=true, so the safety profile is already known. The description adds value beyond that by disclosing the conditional return behavior: a configured flag, a full field set when configured, and just {'configured': False} when the agent has no config. This is useful behavioral context not present in the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core purpose, organized into <summary> and <returns> sections, and every sentence earns its place. The workflow caveat about setup_social_listening and the conditional return shape are both high-value additions with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter read tool with readOnlyHint=true, the description is complete. It even documents the return-dict shape in detail despite there being no output schema, ensuring an agent knows exactly what to expect in both configured and unconfigured cases.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There is only one parameter (agent_id) and the schema describes it fully as 'The agent whose social-listening config to read' (100% coverage). The description adds no additional parameter meaning, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Read') and resource ('an agent's social-listening config'), then enumerates the contained fields (channels, alert terms, subreddits, search spec, writing instructions). It also names the sibling tool it pairs with (setup_social_listening), making it clearly distinguishable from the large sibling set.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says when to use the tool: read the current config before editing with setup_social_listening, because that tool overwrites only the fields you pass. This gives concrete when-to-use guidance plus a workflow hint (carry forward unchanged values), going well beyond a generic statement.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_tag_cross_tabGet Tag Cross TabA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
channelNo'linkedin' or 'email' to bound the population; omit for both.
measureNo'accepted', 'replied', 'interested', or 'meeting'.replied
sendersNoIn a shared workspace, the teammate emails whose outreach to score (a group's `senders`). Omit to score every teammate. Any email not in the workspace is dropped; if that leaves no valid teammate, the result is empty — it does NOT fall back to the whole workspace.
col_kindYes'message' or 'segment', as `row_kind`, for `col_group_id`.
row_kindYes'message' when `row_group_id` is a message tag group (from list_message_tag_groups), 'segment' when it is a segment group (from list_segment_groups).
agent_idsNoScope to one or more campaigns (agent_tasks ids from a group's `campaigns`); omit for all campaigns. Campaign membership is the prospect's current one.
col_group_idYesThe group for the grid columns — a different group from the row one.
row_group_idYesThe group for the grid rows.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description richly discloses behavior: cell denominators include prospects carrying both tags, unclassified segment persons land in no cell, multi-tag prospects appear in multiple cells, only non-empty cells are returned, and swapping axes transposes the grid. It also documents confidence thresholds ('thin' <10, 'early' <30, 'solid'). This is substantial behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but tightly structured with summary and returns sections, and every sentence carries distinct information: examples, id-kind semantics, cell scoring, edge cases, empty-cell behavior, and axis transposition. It is front-loaded with the core purpose and uses the return section to cover output shape without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 8 parameters, no output schema, and only a readOnlyHint annotation, the description supplies what an agent needs: a full return-shape sketch, row/col axis semantics, edge-case handling, and confidence interpretation. The remaining operational details like channel and senders are already covered by the input schema, so nothing critical is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 100% schema description coverage, the schema already documents each parameter, so the baseline is 3. The description adds meaning beyond the schema by explaining that message and segment group ids are numbered separately and travel with their kind, and by giving concrete examples of valid row/col combinations. This helps an agent reason about row_kind/col_kind and group_id pairing.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific operation: crossing two tag groups into a 2-D grid of outcome rates, and distinguishes it from single-dimension rate tools by emphasizing 'the interaction a single-dimension rate hides.' It also clarifies that either side can be a message or segment group, making the tool's identity and scope unmistakable.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context for when to use this tool: when the interaction between two tag groups matters, as opposed to a single-dimension rate. It provides concrete example questions and refers to 'the single-group rate tools' as alternatives, though it does not name them explicitly or state strict when-not-to-use conditions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_tool_connect_urlGet Tool Connect UrlA
Read-only
Inspect

If the user says a service is already connected, call get_integration_status first — the service may already be active and need no link.

A link that opens fine but whose connection then fails is not a link problem — a fresh link routes to the same connect flow and fails the same way. Mint one fresh link and have the user retry once; if it still doesn't connect, the connection is stuck (not a link issue) — call escalate_to_team with the integration and what the user tried rather than re-minting again.

For linkedin, gmail, and outlook the return also includes data_backfill — a sentence stating how far back Sliq syncs history once connected. Relay it with the link so the user knows older data won't appear and how to request a longer backfill; when sharing several connect links in one message, merge the notes into a single short disclaimer instead of repeating one per link. Dict with the connect URL, integration name, the data_backfill note (linkedin/gmail/outlook), and connect_note (linkedin)

ParametersJSON Schema
NameRequiredDescriptionDefault
integration_idYesone of 'gmail', 'outlook', 'linkedin', 'slack', 'hubspot_mcp', 'attio', 'clarify', 'salesforce', 'notion', 'linear', 'fathom', 'granola', 'grain', 'zoom', 'otter', 'fireflies', 'circleback', 'superhuman', 'github', 'asana_mcp', 'atlassian_mcp', 'calendly', 'googledrive', 'googledocs', 'googlesheets', 'reddit', 'exa_websets', 'apollo'

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description discloses important behavioral traits: a fresh link fails the same way as the original if the connection itself is broken, and certain integrations return a data_backfill note that must be relayed. This is genuinely useful context that annotations alone would not provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is larger than a simple one-liner, but nearly every sentence earns its place by conveying workflow or formatting rules. It is reasonably structured with summary and returns sections, though it could be tightened without losing value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers the link lifecycle, retry/escalation behavior, and backfill messaging, which is strong. However, it lists connect_note in the return dict without explaining what it is or whether the agent should relay it, so a small completeness gap remains.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already describes integration_id fully with a list of allowed values, so schema coverage is 100%. The description adds some conditional context about linkedin, gmail, and outlook return values, but it does not add necessary parameter-level semantics beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Generate a URL the user can click to connect a tool.' It clearly differentiates itself from nearby tools by naming get_integration_status and escalate_to_team as related but distinct actions, and states the key output (a clickable connect link).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit when-to-use and when-not-to-use guidance: check get_integration_status first if the user claims a service is connected, and escalate_to_team instead of re-minting links after a failed retry. It also explains how to handle multi-link backfill notes, leaving no ambiguity about the intended workflow.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_user_memoryGet User MemoryA
Read-only
Inspect

The user's saved memory — the durable facts, preferences, and context Sliq has learned about them across conversations. Read it to ground a reply in what's already known before asking the user to repeat something. It is account-wide (shared across all their agents), not per-agent. When none is saved yet, returns a note saying so.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, so the description doesn't need to restate that. It adds valuable context: the scope (account-wide, not per-agent) and the empty-state return (a note saying so). This goes beyond structured fields and helps the agent predict behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, no fluff. The purpose is stated first, then usage guidance, then empty behavior. Every sentence adds information—nothing is repeated or redundant.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple getter with no parameters and no output schema, this is complete. It explains what it returns, its scope, when to use it, and the empty-case behavior. An agent can call it correctly without any additional clarification.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters and schema coverage is 100% (empty object), so the baseline is 4. The description adds meaning by explaining what the memory contains, which is directly useful for deciding whether to call it, even though there are no parameters to document.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool reads the user's saved memory, listing exactly what it contains (durable facts, preferences, context). It distinguishes itself from per-agent memory by explicitly noting it's account-wide. This is a specific verb+resource with enough detail to tell it apart from siblings like save_memory or append_learning.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a clear when-to-use: before asking the user to repeat something, to ground the reply in known facts. It also notes the empty-state behavior. It doesn't explicitly mention alternatives (e.g., search_chat_history for recent context), but for a zero-parameter getter this is sufficient guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_writing_style_guideGet Writing Style GuideA
Read-only
Inspect

Return the user's writing style guide for one channel, to follow when drafting that channel's outreach. Pass channel='linkedin_dm' for a DM or connection note, or channel='email' for an email. A set guide is the user's own voice and overrides the default craft rules; returns a note when none is set.

ParametersJSON Schema
NameRequiredDescriptionDefault
channelYes

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description discloses that a set guide represents the user's own voice, overrides default craft rules, and that a note is returned when no guide is set. This gives an agent useful behavioral expectations without contradicting annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences with no filler: the main purpose is front-loaded, then parameter usage, then the override/fallback behavior. Every sentence adds necessary information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only, single-parameter tool with no output schema, the description covers what is returned, the key channel values, and the fallback behavior. It is slightly light on what a guide actually contains, but this is not essential to invoking the tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description compensates by mapping linkedin_dm to DMs/connection notes and email to emails, adding meaning beyond the bare enum. It does not explain linkedin_comment or reddit_comment, but those labels are self-descriptive in context.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the verb (Return) and resource (user's writing style guide for one channel), and ties it to drafting channel outreach. It does not explicitly differentiate from sibling tools like get_personalization_guide or get_skill_guide, but the resource and channel-scoping make the core purpose unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states when to use the tool (when drafting a channel's outreach) and gives explicit instructions for choosing linkedin_dm vs email. It doesn't list exclusions or mention alternatives, but the context is clear enough for correct selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_agentsList AgentsA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
include_completedNoSet to True to include completed agents.

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare a safe read (readOnlyHint=true), so the description carries the rest of the burden and does so: it discloses the default exclusion of completed agents, the nuance that `trigger_count` counts only triggers that can still fire, and a practical caveat that `result['agents']` is a dict, not directly iterable. This is meaningful behavior beyond annotations, though it stops short of pagination or ordering details.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary sentence is front-loaded and efficient, and the tagged structure aids scanning. There is mild redundancy: the pointer to `get_agent_details` for goal/workspace/trigger bodies appears in both the summary and the returns block.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description correctly assumes responsibility for describing the return value, enumerating every summary field and noting the access pattern via `result['agents']`. For a one-parameter read tool with readOnlyHint, nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the single `include_completed` parameter is already documented there. The description's 'By default excludes completed agents' corroborates the default but adds no syntax or format detail beyond the schema, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (List) and resource (the user's agents) plus the shape of the result ('one-line summaries: status, run counts, and a trigger count'). It also names the distinguishing sibling, `get_agent_details`, so an agent can tell the two apart without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states the default filtering behavior ('By default excludes completed agents') and routes the agent to the alternative: use `get_agent_details(agent_id)` when goal, workspace, or full trigger bodies are needed. When-to-use and the alternative are both spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_attachmentsList AttachmentsA
Read-only
Inspect

List the files in the user's attachment library — id, filename, type, size.

Defaults to global company-context files plus files belonging to the current agent or chat. Set all_agents=True to list the user's whole library across agents. Use a returned id with read_attachment or attach_to_outreach.

ParametersJSON Schema
NameRequiredDescriptionDefault
all_agentsNolist the whole library instead of just the current agent's files.

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations provide readOnlyHint=true, and the description does not contradict it. The description adds valuable behavioral context about default vs. expanded scope, which is beyond the schema, and confirms the fields returned. It doesn't mention permissions or rate limits, but for a simple read-only listing tool this is sufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and front-loaded: the core action and output fields are in the first sentence. Subsequent sentences explain scoping and follow-up usage without redundancy, making it efficient and easy to scan.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-optional-parameter read-only list tool with no output schema, the description is reasonably complete: it states what is returned, how scoping works, and how to use the results. Minor omissions like pagination or limits are not critical given the tool's simplicity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema covers all_agents 100%, so the baseline is 3. The description adds that all_agents=True lists 'the user's whole library across agents' and specifies the default includes 'files belonging to the current agent or chat', providing slightly richer context than the schema's 'current agent's files'.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states the specific operation 'List the files in the user's attachment library' and enumerates the return fields (id, filename, type, size). This clearly differentiates it from sibling tools like read_attachment and attach_to_outreach, which operate on individual attachments.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explains the default scope (global company-context files plus current agent/chat) and how to expand with all_agents=True. It also tells the agent to use returned ids with read_attachment or attach_to_outreach, giving practical downstream guidance, though it doesn't explicitly contrast with all alternative listing/reading tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_classifiable_campaignsList Classifiable CampaignsA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, so the safety behavior is covered. The description adds meaningful behavioral context beyond that: it works before any group exists, returns an empty list when no prospect belongs to a campaign, and only lists the current shared agent when operating one. This helps the agent anticipate actual results.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and front-loaded, stating the core purpose in the first sentence and adding only relevant usage context afterward. The returns section is a single focused sentence. No filler or redundant restatement of the title exists.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter, read-only tool with no output schema, the description is complete: it states what the tool returns, the exact data shape, the empty-case behavior, when to use it, and when to use the alternative. An agent can invoke it correctly without additional inference.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters and schema description coverage is 100%, so there is no parameter ambiguity to resolve. The description still adds semantic value by explaining that the result feeds criteria.agent_ids, which helps the agent understand how to use the returned data.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the action ('List every campaign') and the exact resource scope ('the user's (team-wide) prospects belong to'), then explains why this output matters ('the options for a new message tag group's or segment group's criteria.agent_ids'). This distinguishes it from sibling list tools like list_agents or list_message_tag_groups by tying it to a specific downstream use.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit usage timing: 'read it when setting up the first group' and clearly states when to prefer an alternative ('once groups exist, each group's classifiable_campaigns carries the same list'). The shared-agent scoping note adds another practical decision rule.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_message_tag_groupsList Message Tag GroupsA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, and the description adds substantial behavioral context: the meaning of 'stale', 'classify_running' and its progress counters, what 'criteria' represents, and how campaigns/senders behave under a shared agent. No contradiction with annotations exists.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the summary and usage purpose, then uses a structured <returns> block to document a complex return object. The detailed field explanations are necessary given the rich nested schema and no formal output schema, with no filler or redundant prose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite having no parameters or formal output schema, the description is complete: it specifies the return shape, explains each meaningful field, flags state semantics (stale, classify_running), and covers the shared-agent edge case. An agent has everything needed to call this tool and interpret its results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has zero parameters, so there is nothing to document; the description correctly focuses on the returned fields and how they feed downstream tools. It explains that the returned group_id is the prerequisite for rate/message tools, which is useful despite not being a parameter of this tool.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'List the user's message tag groups', and further clarifies what those groups are ('dimensions their sent outreach is classified along') and what is included ('each with its tags and its tagging status'). This distinguishes it from related tools like create/update/delete_message_tag_group and classify_message_tag_group.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The summary explicitly instructs agents to 'Read this first to get a group_id for the rate / message tools', establishing when this list tool should be called. It also points to related actions (update_message_tag_group, classify_message_tag_group, get_message_tag_rates, query_tagged_messages), though it does not state explicit exclusion conditions versus those alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_prospect_eventsList Prospect EventsA
Read-only
Inspect

Spans every campaign by default; pass agent_id to scope to one. To page further back, re-call with before set to the oldest returned event's occurred_at. Dict with count, a summary block (replies_this_week, accepts_this_week, posts_found_this_week, meetings_booked_this_week), and an events array. Events are newest-first; each is {id, kind, channel, owner_email, occurred_at, time_precision, subject_type, snippet, turn, prospect: {id, name, linkedin_url, provider_id, title, company, has_conversation}, agent: {id, title} | null, queued_followup: {...} | null}. turn is 'user' when a draft awaits the user's approval, 'sliq' when a send is queued, else 'none'; queued_followup carries that drafted next step when one exists. prospect.id is null when the event isn't attributable to a tracked row.

ParametersJSON Schema
NameRequiredDescriptionDefault
kindsNoSources to include — any of 'accept', 'linkedin_reply', 'email_reply'. Omit for all three.
limitNoMax events (default 50, capped at 200). Page older with `before`.
beforeNoISO 8601 timestamp cursor; only events strictly older are returned (pass a prior page's oldest `occurred_at`).
agent_idNoScope the events and the summary to one campaign. Omit for the cross-campaign feed. Note `turn` stays prospect-scoped — it can read 'user' off a draft awaiting approval under a different campaign for the same person; `queued_followup` is campaign-matched and won't.
as_teammateNoRead a consented teammate's activity instead of your own — pass their email. Gated on that teammate's conversation-sharing setting; a teammate who hasn't shared is rejected. Omit for your own.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only provide readOnlyHint:true, so the description carries the burden of behavioral disclosure. It adds key traits: the feed is newest-first, spans all campaigns by default, supports pagination via `before`, and includes a 7-day summary. It also explains the semantics of `turn` and `queued_followup` in the returns, which goes well beyond a simple read-only hint.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but highly structured, with a `<summary>` block and a detailed `<returns>` section. It front-loads the core purpose and usage examples, then follows with pagination and return details. Every sentence contributes value; however, the returns block is dense and could be seen as slightly over-detailed for an initial reading. It earns a 4 rather than 5 because it is not as concise as a two-sentence definition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description must explain return values, and it does so thoroughly: count, summary fields, event object structure, and the meaning of `turn` and `queued_followup` are all explained. It also covers pagination (`before`), default scoping, and the null behavior of `prospect.id`. Given the tool's complexityches, this is exceptionally complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already provides 100% coverage of all 5 parameters, so the baseline is 3. The description adds meaningful context beyond the schema for two important parameters: `agent_id` (default scoping to all campaigns) and `before` (pagination technique). It does not redundantly explain every parameter, but the added usage guidance for these two lifts the score above baseline.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Read the cross-channel prospect activity feed,' and enumerates the exact union of event types (connection accepts, LinkedIn replies, email replies) with accompanying annotations. It differentiates from alternatives by positioning itself as a single call 'instead of stitching the LinkedIn, email, and queue readers together by hand,' which clearly identifies what this tool is not.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly states when to use the tool: 'Use it to answer "who replied this week", "what just happened across my campaigns", "who's waiting on me", or "did anyone accept" in one call.' It names the alternative approach ('stitching the LinkedIn, email, and queue readers together by hand'), giving a clear contrast. The scoping and pagination instructions ('pass agent_id to scope to one', 're-call with before set to the oldest returned event's occurred_at') further guide usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_segment_groupsList Segment GroupsA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond readOnlyHint, disclosing shared-agent scoping behavior (campaigns/senders limited to the current shared agent and its owner), the meaning of stale and classify_running/classify_done/classify_total, and that criteria is the persisted auto-tagging scope. This is unusually rich behavioral context for a read tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded summary followed by clearly tagged <returns> structure. It is long, but the field-level detail substitutes for a missing output schema, so most sentences earn their place; minor verbosity in enumerating every field.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, so the description fully carries the return-value burden and does so thoroughly, enumerating the response shape and field meanings. Nothing an agent needs to use this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Zero parameters, so the baseline of 4 applies. The description instead explains return-field semantics (criteria, stale, campaigns, classifiable_campaigns, senders) that inform which tools to call next, adding value even without params.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource (list the user's segment groups) and explains what a segment group is with concrete examples ('Seniority', 'Role function'), distinguishing it from create/update/delete/classify siblings. An agent knows exactly what this returns without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says 'Read this first to get a group_id for the rate / people tools', routing the agent to get_segment_rates and query_segment_people, and names update_segment_group as the editing alternative. Both the call condition and the alternatives are stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_sent_messagesList Sent MessagesA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
qNoCase-insensitive text to find in the message or the prospect's name.
limitNoPage size, at most 200.
offsetNoPage offset. `total` is the count before paging, so the whole tail is reachable.
tag_idNoOnly messages carrying this tag (from list_message_tag_groups); takes precedence over tag_state.
sendersNoIn a shared workspace, the teammate emails (yours included, if wanted) whose messages to list; omit for only your own. A teammate who doesn't share conversations, or an email outside the workspace, is dropped; if none remain the list is empty.
positionsNo'first', 'follow_up' and/or 'reply' to narrow by message type; omit for all. A connection-request note is always 'first'.
tag_stateNo'all', 'tagged' (at least one tag in any group) or 'untagged' (no tags; a "No match" result counts as untagged).all
prospect_idNoOnly messages sent to this prospect.

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=true, so the description carries the behavioral burden and does so well: default scope is the user's own messages, `senders` expands to teammates, non-sharing teammates or out-of-workspace emails are silently dropped, and a shared agent is scoped to that agent's campaign. It stops short of describing pagination edge cases, but the filtering semantics are transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the one-line scope summary before use cases, and the return shape sits in a separate <returns> block rather than bloating the prose. It is a bit long, but each sentence (scope, senders behavior, tagging use cases, shared-agent caveat) carries distinct information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an 8-parameter, no-required-arg list tool with no output schema, the description is complete: it defines the result set, the default filtering behavior, and the full return shape (totals, counts, per-message fields, nested tags/groups/outcome) with clarifying notes on `sender`, `groups`, and `outcome`. Nothing an agent needs to call it or interpret the response is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3, but the description adds value beyond the schema: it motivates `tag_state='untagged'` for finding untaggable items, explains why `senders` may yield an empty list, and ties `tag_id` and `q` to the tagging workflow. The added context is meaningful rather than redundant.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb and resource with explicit scope: 'Every message the user sent to a prospect, tagged or not, each with its tags from every tag group.' It anchors this to the Analytics → Messages tab, so an agent knows exactly what set this returns and can distinguish it from tagging/mutation siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete when-to-use cases (find untagged messages via tag_state='untagged', read tags across groups, locate a message to tag) and routes to the sibling correct_message_classification. It does not, however, differentiate from the closely related query_tagged_messages sibling, so the routing guidance is not exhaustive.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_teammatesList TeammatesA
Read-only
Inspect

The caller's active-workspace teammates — the same roster the Team page shows.

Returns {'teammates': [{'email', 'display_name', 'role' ('admin'|'member'), 'status' ('invited'|'active'), 'is_you', 'joined_at', 'invited_by', 'share_conversations', 'allow_team_actions'}, ...]}, ordered by join time. Includes the caller (flagged 'is_you'). A solo user with no workspace gets {'teammates': []}.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description discloses the exact response shape, field meanings including role and status enums, ordering by join time, inclusion of the caller flagged with 'is_you', and the solo-user edge case returning an empty list. This is valuable behavioral context not available from the schema or annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured and front-loaded: it states the core purpose first, then the return object, then edge cases. Every sentence adds necessary information, and nothing is redundant or extraneous.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, so the description carries the full burden of explaining the return value. It lists all returned fields, their value constraints, ordering, the caller's inclusion, and the empty-list edge case, making the tool fully understandable for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters and the input schema is complete with additionalProperties false, so there is nothing to document. The description appropriately focuses on output semantics instead of inventing parameter guidance, warranting the baseline score for a no-parameter tool.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb and resource: it lists 'the caller's active-workspace teammates.' It further clarifies scope by comparing it to 'the same roster the Team page shows,' making the tool's purpose immediately distinct from sibling tools like list_team_shared_agents or list_tasks.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description makes the intended use clear: retrieving the current workspace's teammate roster. It does not explicitly name alternatives or state when not to use the tool, but the uniqueness of the resource and the clear scope provide sufficient context for an agent to select it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_team_shared_agentsList Team Shared AgentsA
Read-only
Inspect

Team-shared agent visibility: teammates' agents plus the agents shared with and by the caller.

Returns {'teammates_agents': {owner_email: [{'id', 'title', 'status', 'template_id'}, ...], ...}, 'shared_with_me': [{'share_id', 'access_kind', 'owner_email', 'owner_display_name', 'agent': {'id', 'title', 'status'}, 'created_at'}, ...], 'shared_by_me': [{'id', 'agent_id', 'owner_email', 'owner_display_name', 'shared_with_email', 'shared_with_display_name', 'created_at'}, ...]}. teammates_agents is grouped by teammate email and gated on allow_team_actions — only teammates who let you act on their behalf appear, so every listed agent is openable. shared_with_me are agents others shared with you; shared_by_me are your own agents you've shared, one row per recipient. A solo user with no shares gets an empty grouping and empty lists.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare readOnlyHint=true, and the description adds meaningful behavioral context: the `teammates_agents` grouping is gated on `allow_team_actions`, so only openable agents appear. It also explains the one-row-per-recipient semantics for `shared_by_me` and the empty result for solo users. This goes beyond the annotation and helps the agent understand the data shape and access implications.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is detailed but well-organized: it front-loads the purpose, then provides a compact return schema, then explains the semantics of each group. Every sentence adds value, though the inline schema is somewhat dense. It is longer than strictly necessary but justified by the complex return structure.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter read-only tool, the description is nearly complete: it explains the return shape, the grouping, the access gating, and the empty case. It does not explicitly state that no input is required, but the empty schema already conveys that. The only minor gap is not describing any pagination or limits, but that is not critical for this tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the schema provides no parameter semantics. The description compensates by thoroughly explaining the return structure and the meaning of each field group, which is the relevant semantic content for a no-parameter tool. Baseline 4 is appropriate because there are no parameters to document.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool lists team-shared agents and distinguishes three categories: teammates' agents, agents shared with the caller, and agents shared by the caller. It uses specific verbs and resource terms, and the detailed return structure makes the purpose unambiguous. It also differentiates from sibling tools like list_agents and list_teammates by focusing on shared-agent visibility.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains what the tool returns and the gating condition (`allow_team_actions`), which implies when it is useful: when the caller needs to see openable teammates' agents or shares. It does not explicitly state when not to use it or name alternatives, but the context is clear enough for an agent to select it appropriately.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

manage_email_outreach_queueManage Email Outreach QueueA
Destructive
Inspect

Bulk operations (status, approve-all, cancel_all) act on the current agent's queue when run inside an agent, and across the whole account from account-level chat — except a bulk cancel_all or approve-all, which from account-level chat is rejected unless agent_id names the campaign, so it can't hit every campaign at once. Targeting one person by recipient_email reaches them in any agent. The next send time and per-mailbox usage stay account-wide (the send rate is per-mailbox).

For pausing/resuming the campaign, use update_agent to set the agent status to 'paused' or 'active' instead. Dict with action result and current queue status

ParametersJSON Schema
NameRequiredDescriptionDefault
bodyNoFor 'update' — new body text.
actionYesOne of: - 'status': Get full queue details (pending/sent/failed items). The `pending_items` / `pending_approval_items` lists cap at 50 each, each with a `pending_truncated` / `pending_approval_truncated` flag — when a list's flag is True, pass `offset` (offset += 50) to read the next page. Each item carries the `mailbox` it sends from; the account-level `mailboxes` list gives each mailbox's remaining daily headroom. The top-level `cancelled_by_kind` maps each cancel cause to its count (`recipient_replied`, `task_deleted`, `auto_shelved`, …; pre-taxonomy rows under `unknown`) — read it to answer "how much outreach was cancelled, and why?". - 'update': Edit a queued email's subject and/or body. Requires recipient_email. Pass subject and/or body to change — no cancel-and-re-queue needed. Send order is derived, not per-row scheduled; to pause or resume the campaign use update_agent (see the note above). - 'approve': Send authorization belongs to the user, so this action needs a message that arrived after the drafts were queued and explicitly says to approve or send them. A message from before the drafts existed cannot authorize them — a request to review them, to approve them later, or to be able to approve from chat means: report that the queue is awaiting approval and end your turn; the user's next message decides. Once authorized: if recipient_email is given, approve only that item, otherwise approve ALL pending_approval items. From account-level chat an approve-all is rejected unless agent_id names the campaign — it must not fire every campaign's drafts into sending at once. - 'cancel': Cancel a queued email. Pass recipient_email to cancel that one person. To cancel the ENTIRE pending queue — which drops drafts the user already approved — you must pass cancel_all=True; an unscoped cancel without it is rejected. From account-level chat a cancel_all is rejected unless agent_id names the campaign to cancel — it must not clear every campaign's queue at once.
offsetNoFor 'status' only — skip this many items in each queued list before returning the next 50. Use it to page through a queue larger than 50 (offset=50 for items 51-100, etc.).
subjectNoFor 'update' — new subject line.
agent_idNoFor 'cancel' and 'approve' — scope a bulk cancel_all / approve-all to one campaign's queue. Required from account-level chat (no active agent), where an unscoped bulk cancel or approve is rejected; call list_agents to get the id.
cancel_allNoFor 'cancel' only — confirm a queue-wide cancel when no recipient_email is given. Guards against silently tearing down the whole campaign.
as_teammateNoAct on a consented teammate's email queue instead of your own — pass their email. Covers approve / cancel / update / status; a bulk approve-all / cancel_all must name one of the teammate's campaigns via agent_id. Gated on that teammate's act-on-behalf setting; a teammate who hasn't granted it is rejected. Omit for your own.
recipient_emailNoFor 'update', 'approve', and 'cancel' — target a specific recipient. Leave empty to apply to all items (not valid for 'update'; for 'cancel', requires cancel_all=True).

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations (readOnlyHint=false, destructiveHint=true), the description discloses important behaviors: staggered send timing, cancel-all dropping already-approved drafts, approval requiring explicit authorization arriving after drafts were queued, per-mailbox rate limits, and pagination truncation behavior. No contradiction with annotations exists.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long, but the length is warranted for an 8-parameter tool with destructive and account-wide behavior. Major constraints are front-loaded in the summary, and action-specific detail is organized in the action enum. It could be tightened with bullet formatting, but every sentence carries meaningful guidance.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex, potentially destructive tool with no output schema, the description covers all critical context: authorization requirements, account vs. agent scope, cancel guards, pagination, mailbox headroom, return content, and the alternative tool for pausing/resuming. Nothing an agent needs to call it safely is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds cross-parameter semantics: cancel_all is required for unscoped cancels, agent_id scopes account-level bulk actions, recipient_email targets a single person across any agent, and offset uses 'offset += 50' as a pagination recipe. These relationships go beyond individual schema property descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Manage the email outreach queue' and enumerates the concrete operations: 'check status, update/cancel items, or approve pending items.' It also distinguishes itself from the closely related sibling update_agent by explicitly routing pause/resume behavior to update_agent instead.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit when-to-use context: bulk operations scope to the current agent's queue inside an agent vs. the whole account from account-level chat, account-level cancel_all/approve-all are rejected unless agent_id names the campaign, and pausing/resuming should use update_agent. These exclusions and alternatives leave little to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

manage_inboxManage InboxBInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
actionYesThe operation to perform. One of: - 'list_labels': List available labels (Gmail) or categories (Outlook). - 'apply_label': Apply a label/category to emails. Creates if it doesn't exist. Requires email_ids and label_name. - 'remove_label': Remove a label/category from emails. Requires email_ids and label_name. - 'archive': Archive emails (removes from inbox, keeps in account). Requires email_ids. - 'mark_read': Mark emails as read. Requires email_ids. - 'unsubscribe': Unsubscribe from a mailing list via List-Unsubscribe header. Requires email_ids (one email). ALWAYS confirm with the user before calling this. - 'search_by_label': Search emails by label (Gmail) or category (Outlook). Requires label_name. Returns matching emails directly from the provider API.
mailboxNoEmail address of a connected mailbox. Optional — when not given and email_ids are present, the mailbox is inferred from the email row's connected_email. When neither is available and the user has multiple mailboxes, ask which to use.
providerNoWhich email account to use ('outlook' or 'gmail'). Only needed if the user has both connected and no email_ids are provided.
email_idsNoList of email IDs from search_emails results.
label_nameNoLabel name for apply_label, remove_label, or search_by_label actions.
as_teammateNoAct on a consented teammate's inbox instead of your own — pass their email. Every action (label, archive, mark read, unsubscribe) runs against their mailbox. Gated on that teammate's act-on-behalf setting (stricter than conversation sharing); a teammate who hasn't granted it is rejected. Omit for your own inbox.
max_resultsNoMaximum number of emails to return for search_by_label (default 20, max 50).
exclusive_labelsNoFor apply_label only. A set of mutually exclusive labels (e.g. inbox organization categories). When provided, applying a label automatically removes any other exclusive_labels from older messages in the same thread. This keeps one label per thread.

TDQS

B3.4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations indicate the tool is not read-only and not destructive, and the description's action list conveys that it mutates mailbox state. It does not disclose side-effect details in the description itself, such as auto-creating labels, archive removing emails from the inbox but keeping them in the account, or the List-Unsubscribe side effect; those details live only in the schema, so the description adds partial behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single front-loaded sentence enumerating key capabilities plus a short return note, with no filler or redundancy. It could be slightly more specific by replacing the generic 'Manage' verb, but it is appropriately sized and easy to scan.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with seven actions and eight parameters, the top-level description is thin, and the return contract ('Dict with action-specific results') is vague. The input-schema action descriptions fill most invocation gaps, but with no output schema, per-action result shapes are never explained, making the definition adequate rather than complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the action enum includes detailed per-action requirements, so the structured fields already document all eight parameters thoroughly. The top-level description adds no parameter-level meaning, so the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a verb ('Manage') plus a resource ('emails in the user's inbox') and lists the major supported operations: labeling, archiving, marking as read, and unsubscribing. This makes it identifiable as an inbox-mutation tool rather than a read/search tool. It is not a 5 because 'Manage' is broad and it does not explicitly differentiate itself from email siblings like send_email, search_emails, or get_emails.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied through the action enum and parameter descriptions, which specify required arguments per action and call out that unsubscribe requires user confirmation. However, the top-level description gives no explicit guidance on when to prefer this tool over email read/search/send alternatives, nor does it state exclusions or when not to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

manage_linkedin_invite_queueManage Linkedin Invite QueueA
Destructive
Inspect

Bulk operations (status, approve-all, cancel_all) act on the current agent's queue when run inside an agent, and across the whole account from account-level chat — except a bulk cancel_all or approve-all, which from account-level chat is rejected unless agent_id names the campaign, so it can't hit every campaign at once. Targeting one person by target_provider_id reaches them in any agent. Pacing / next_scheduled / daily usage stay account-wide (the LinkedIn rate limit is per-account). Dict with action result and current queue status

ParametersJSON Schema
NameRequiredDescriptionDefault
actionYesOne of: - 'status': Get full queue details (pending/sent/failed items with names and times). The `pending_items` / `pending_approval_items` / `failed_items` lists cap at 50 each, each with a matching `*_truncated` flag — when a list's flag is True, more rows exist past this page; pass `offset` (offset += 50) to read them. Each `failed_items` row carries `last_error`, the reason that send failed — read it to answer "why wasn't X sent?". `pending_approval_items` rows also carry `last_error` when the row was held for a reason beyond plain approval gating (e.g. the resolver's name-mismatch hold). Queued post comments and reactions carry `post_url` and a `post_text` excerpt of the post the action targets, so you can tell which post a queued comment is on; `post_text_truncated` True means the target post ran longer than the excerpt shown. All four lists share one field shape; a `resolving_items` row (profile not yet resolved) has a blank `target_provider_id` and its `message` is the copy that will send once it resolves — edit or cancel it by its `target_identifier`. Every `pending_items` / `resolving_items` row carries `projected_send_day` + `projected_send_time` (a resolving row's is its projected lookup time, or its projected send time once it's a resolve-then-send) and a `queue_position` — the row's place in the one ordered lookup+send timeline, so the two lists interleave into what actually fires next. The top-level `cancelled_by_kind` maps each cancel cause to its count (`recipient_replied`, `task_deleted`, `auto_shelved`, …; pre-taxonomy rows under `unknown`) — read it to answer "how much outreach was cancelled, and why?". - 'update': Update a queued item's message text. Name the item by target_provider_id (a pending row's pid) OR, for a resolving row that has no pid yet, by target_identifier (its URL/slug, shown on the row). Use this to edit a queued message — including on resolving rows, which still hold the message that will send once their profile resolves. When a target has more than one queued action — a post with both a comment and a reaction, or a person with a connection request and a follow — also pass action_type to name which one to edit. - 'approve': Send authorization belongs to the user, so this action needs a message that arrived after the drafts were queued and explicitly says to approve or send them. A message from before the drafts existed cannot authorize them — a request to review them, to approve them later, or to be able to approve from chat means: report that the queue is awaiting approval and end your turn; the user's next message decides. Once authorized, this moves pending_approval items to 'pending'; they then send in queue order at your account's pace. If target_provider_id is given, approve only that item. Otherwise approve ALL pending_approval items (optionally filtered by action_type). Only touches items already awaiting approval — it does NOT run pending profile lookups or pre-approve resolving rows (those aren't in the approvals UI, so nobody has reviewed them; they resolve on their own schedule and, if the user's settings gate the action, surface for approval once resolved). From account-level chat an approve-all is rejected unless agent_id names the campaign — it must not fire every campaign's drafts into sending at once. - 'cancel': Cancel a queued item. Pass target_provider_id to cancel that one person or post — every queued action for them — or target_identifier to cancel a resolving row that has no pid yet. To cancel the ENTIRE pending queue — which drops drafts the user already approved — you must pass cancel_all=True; an unscoped cancel without it is rejected (an action_type or node_id filter alone is still a bulk cancel and also needs cancel_all=True). Narrow a cancel_all to one flow stage with node_id (e.g. restart only the m2 messages without touching m3) — the only way to separate two stages that share an action_type. From account-level chat a cancel_all is rejected unless agent_id names the campaign to cancel — it must not clear every campaign's queue at once. - 'pause': Pause the queue (stops sending, keeps items queued). Pass resume_at to schedule an automatic resume ("pause while I'm on vacation, resume July 20"); without it the pause is indefinite and only an explicit 'resume' restarts sending. - 'resume': Resume the queue (pending items drain in queue order at your account's pace)
offsetNoFor 'status' only — skip this many items in each queued list before returning the next 50. Use it to page through a queue larger than 50 (offset=50 for items 51-100, etc.).
messageNoFor 'update' — new message text. 300 chars max for connection requests.
node_idNoFor 'cancel' only — narrow a cancel_all to the queue rows on one flow node (the row's flow stage, e.g. 'm1', 'm2'), parallel to action_type. action_type is the send mechanism (connection_request / message / inmail / ...), not the stage, so every message stage shares action_type='message' — node_id is the only handle that isolates one (m1 and m2 are both action_type='message', so only node_id restarts m1 without touching m2). Still a bulk cancel — needs cancel_all=True, not a substitute for it.
agent_idNoFor 'cancel' and 'approve' — scope a bulk cancel_all / approve-all to one campaign's queue. Required from account-level chat (no active agent), where an unscoped bulk cancel or approve is rejected; call list_agents to get the id.
resume_atNoFor 'pause' — ISO datetime when sending should automatically resume. Must be in the future. A naive datetime is interpreted in the user's timezone. Use resolve_date first for natural language ("July 20", "in 2 weeks").
cancel_allNoFor 'cancel' only — confirm a queue-wide cancel when no target_provider_id is given. Guards against silently tearing down the whole campaign.
action_typeNoThe kind of queued action to target, as shown on each queue row in the 'status' result (e.g. 'comment', 'reaction', 'connection_request', 'follow'). For 'update', pass it when a target carries more than one queued action so the right row is chosen. For 'approve' and 'cancel', it narrows the bulk operation to one kind — a bare target_provider_id on 'cancel' cancels every queued action for that person or post. Not a substitute for cancel_all.
as_teammateNoAct on a consented teammate's queue instead of your own — pass their email. Covers approve / cancel / update / status; pause and resume (account-wide controls) are not available on a teammate's behalf. A bulk approve-all / cancel_all must name one of the teammate's campaigns via agent_id. Gated on that teammate's act-on-behalf setting; a teammate who hasn't granted it is rejected. Omit for your own.
target_identifierNoFor 'update' and 'cancel' of a resolving row (profile not yet resolved, blank target_provider_id) — the row's URL/slug, shown on the row in the 'status' result. Ignored when target_provider_id is set.
target_provider_idNoFor 'update' and 'cancel' — the person's pid or post URN to target. A resolving row has no pid yet — target it by target_identifier instead. For 'cancel', leave empty (with cancel_all=True) to cancel all pending items.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already flag readOnlyHint=false and destructiveHint=true; the description adds substantial behavior beyond that: randomized human-like send intervals, per-user activity-window enforcement, account-wide rate-limit pacing, the cancel_all guard ('Guards against silently tearing down the whole campaign'), approve authorization timing ('needs a message that arrived after the drafts were queued'), and as_teammate permission gating. Nothing contradicts the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with purpose and is well-blocked by action, but it is long and contains noticeable redundancy: the account-level chat rejection rule appears in the summary, the 'approve' action, and the 'cancel' action; resolving-row behavior appears in both the summary and the 'status' action. The complexity justifies much of the length, but tighter deduplication would help.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description carries the full burden of explaining return values and does so thoroughly: 50-item list caps with *_truncated flags, last_error on failed items, projected_send_day/time and queue_position interleaving, cancelled_by_kind mapping, and scoping/authorization edge cases. Nothing needed for correct invocation is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, setting a baseline of 3. The description adds value above the schema by explaining the queue model (activity window, pacing, pre-send stages) and the cross-parameter relationships (why agent_id is required for account-level cancels, why node_id exists when action_type is shared across stages, and how target_identifier substitutes for target_provider_id on resolving rows). This interpretive context moves it above baseline.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb+resource ('Manage the LinkedIn outbound queue') and goes well beyond a tautology by explaining what the queue is (pre-send LinkedIn actions sent at randomized intervals). It explicitly differentiates itself from siblings: 'The queue only knows the pre-send stages — for a prospect's full lifecycle... read tracking items with query_prospects,' and it is clearly distinct from manage_email_outreach_queue by domain.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit when-to-use guidance ('Use this tool to check status, update/cancel items, or pause/resume the queue') and an explicit when-not-to-use with a named alternative (query_prospects for post-send lifecycle). It also covers contextual scoping rules: agent vs account-level chat behavior, bulk-operation restrictions, and teammate act-on-behalf limits.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

mark_monitor_events_notifiedMark Monitor Events NotifiedAInspect

Mark monitor events as included in a digest notification. Call this after composing a Slack digest to prevent re-notifying the same events. Only affects events belonging to the current user; in an agent run it scopes to the run's agent.

ParametersJSON Schema
NameRequiredDescriptionDefault
event_idsYesList of agent_monitor_events IDs to mark as notified

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only indicate readOnlyHint=false and destructiveHint=false. The description adds meaningful behavioral context: it marks events as included in a digest, affects only the current user/run's agent, and prevents re-notification. It does not contradict annotations and provides useful side-effect information beyond the structured data.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with no wasted words. The purpose is front-loaded, followed by the timing and scope constraint. Every sentence contributes useful guidance.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple, single-parameter tool with no output schema, the description covers all essential aspects: what it does, when to call it, why it is needed, and the scope of its effect. It is complete enough for an agent to invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already fully describes the single parameter (event_ids) with 100% coverage, including its type and purpose. The description does not add further semantic meaning to the parameter beyond what the schema provides, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the action ('Mark monitor events as included in a digest notification') with a specific verb and resource. It is unambiguous and distinct from the sibling mark_social_posts_notified, which targets a different resource type.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly states when to call this tool ('after composing a Slack digest') and provides a clear rationale ('prevent re-notifying the same events'). It also gives important scope guidance (current user/run's agent). However, it does not explicitly mention alternatives or when-not-to-use, so it stops short of a full 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

mark_social_posts_notifiedMark Social Posts NotifiedAInspect

Mark social listening matches as included in a digest notification. Only affects posts belonging to the current user; in an agent run it scopes to the run's agent.

ParametersJSON Schema
NameRequiredDescriptionDefault
post_idsYesList of agent_social_listening IDs to mark as notified

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations indicate readOnlyHint=false and destructiveHint=false, so the agent already knows this is a safe write. The description adds useful behavioral context by clarifying the ownership scope and agent-run scoping, which is not available in the annotations or schema. No contradiction found.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two terse sentences with no filler. The primary action and resource are front-loaded, and the scoping caveat is positioned second. Every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple one-parameter mutation with annotations covering safety, the description and schema together provide enough to call the tool correctly. The digest-notification context is not expanded, but that is a minor omission given the tool's low complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the single parameter post_ids is already fully documented as 'List of agent_social_listening IDs to mark as notified.' The tool description adds no extra parameter meaning beyond the schema, so the baseline score of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('mark') and a specific resource ('social listening matches') plus the semantic outcome ('as included in a digest notification'). This clearly distinguishes it from the closely named sibling mark_monitor_events_notified by focusing on social listening matches rather than monitor events.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides an explicit scoping rule: 'Only affects posts belonging to the current user; in an agent run it scopes to the run's agent.' This functions as a when-not condition (do not use for other users' posts), but it does not name an alternative tool or explain broader selection context, so it falls slightly short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

matches_icpMatches IcpA
Read-only
Inspect

Runs the two mechanical checks — employee-count band and funding-stage normalized membership, both include-on-null — and combines them with the caller's industry and location judgments. Industry fit and location are NOT decided here: the agent judges both and passes its verdicts in. {passed, location_ok, size_ok, size_null_included, size_contested, industry_ok, funding_stage_ok, funding_stage_null_included}. passed is the AND of location_ok, size_ok, industry_ok, funding_stage_ok.

Size is not a pure hard gate — it includes-on-uncertain-data like funding, because Apollo undercounts privately-held / industrial firms:

  • size_null_included True → Apollo had no headcount; included anyway.

  • size_contested True → Apollo's count is below the smallest band floor (probably an undercount); included anyway. Either flag → score that Company-size evaluation 5 (amber "verify"), not

  1. A count in a gap between bands or above the ceiling is a confident mismatch → size_ok False (drop). Score Industry from industry_verdict (fit → 10, uncertain → 5) and Location from location_verdict (in → 10, uncertain → 5).

ParametersJSON Schema
NameRequiredDescriptionDefault
employee_countNoCandidate headcount. None when unknown.
industry_verdictYesThe agent's industry judgment — 'fit' (confidently in an ICP industry), 'uncertain' (plausibly in, but signals thin or conflicting — kept and flagged), or 'off' (drop).
location_verdictYesThe agent's judgment of whether the candidate's HQ lies within the configured ICP locations — 'in', 'uncertain' (plausibly in, but the location data is partial or it sits just outside a listed place — kept and flagged), or 'out' (drop).
icp_funding_stagesNoConfigured funding stages — optional; unset or empty makes the funding check a no-op (everything passes it).
latest_funding_stageNoApollo's title-case stage string ('Series B', 'Seed'); None/absent under patchy coverage.
icp_employee_count_rangesYesConfigured size ranges, each a 'min,max' pair ('25,200'), an empty side meaning unbounded ('500,', ',50').

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With readOnlyHint=true already covering the safety profile, the description adds substantial behavioral detail: it explains the include-on-null behavior for both size and funding, the size_contested/size_null_included flags, the scoring rules (amber 'verify' vs drop), and the exact output semantics. This goes far beyond the annotation and gives the agent a precise mental model of edge cases and how to interpret results.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is structured with summary and returns sections, front-loading the core purpose. It is detailed but every sentence adds value, covering the mechanical checks, the agent's role, and the output interpretation. Slightly longer than minimal, but the complexity justifies the length.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has 6 parameters, no output schema, and subtle behavior (include-on-null, contested size), the description thoroughly covers the return structure, the logic for each flag, and how to interpret scores. An agent has everything needed to call this correctly and use the result without ambiguity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description does add some contextual value by explaining how industry_verdict and location_verdict map to scores (fit→10, uncertain→5) and how icp_employee_count_ranges are used for the size band check, but it largely reinforces schema descriptions rather than providing new meaning. It does not compensate with additional parameter-level insights beyond what the schema already states.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('matches ICP') and resource (per-candidate gate), and explicitly scopes it to 'find-companies News mode.' It distinguishes its role by clarifying that industry and location are NOT decided here, which sets it apart from any potential judgment tools. The purpose is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly identifies the intended usage context ('per-candidate ICP gate for find-companies News mode') and instructs the agent to pass its own industry and location verdicts, implying the prerequisite that the agent must make those judgments first. It does not explicitly name alternatives, but the context is sufficiently clear that an agent would know when to invoke this tool (during candidate filtering in find-companies) and that it relies on the agent's prior judgments. No misleading guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

move_prospect_to_nodeMove Prospect To NodeAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
node_idYesA connection_request / send / action / manual / automation / decision / terminal node id in the agent's sequence (not start).
agent_idYesID of the agent whose flow the prospect is on.
prospect_idYesThe prospect's `id` — the outreach_prospects primary key, as returned on every query_prospects row and shown on the flow roster line.

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses side effects beyond the annotations: moving the prospect fires the node's on_enter enactment on the next run, each call appends a fresh position row, and calling again re-routes while keeping prior history. This is valuable behavioral context that annotations don't provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but structured with a <summary> tag and uses every sentence to explain important behavior or exclusions. It is slightly verbose for a tool with only three parameters, but the density of information justifies the length.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers the core purpose, when to use, side effects, and return shape, and it references a <returns> section. It doesn't explicitly explain what happens with a terminal node or error conditions, but these are minor given the detailed operational behavior already provided.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the schema already explains each parameter (node_id, agent_id, prospect_id). The description adds no additional parameter-level detail beyond what's in the schema, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Move a prospect onto a flow node the automatic position writes can't place them on' and enumerates the node types it accepts. This clearly distinguishes it from automatic position-writing behavior and from other prospect-related tools in the sibling list.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly states when to use the tool: 'the decision arm you picked after judging a fork, or the `then` target after an automation step's side-effect' and when not to: 'so do not compose the send here.' It also explains which cases the automatic paths already cover, giving a clear decision rule.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_analyticsQuery AnalyticsA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
daysNoNumber of days of snapshots to look back (default 7). Use `days=0` to fetch only the most recent snapshot — which under the daily-window collector covers yesterday UTC. This is the right call for the setup-time "show the user a sample of yesterday's data" flow, since it matches what the scheduled digest will show. Ignored for metric="digest_view".
metricYesOne of "traffic", "sources", "top_pages", "top_clicks", "engagement", "daily_digest", "digest_view", "custom". Use "digest_view" to read exactly what the user sees on this agent's Analytics Output tab — the derived today + history entries (each with week-over-week deltas) + PostHog link; the other metrics return raw snapshot data.
custom_queryNoRaw HogQL SQL query (only for metric="custom"). Must be a valid SELECT statement, e.g. "SELECT properties.$pathname as path, count() as views FROM events WHERE event = '$pageview' GROUP BY path ORDER BY views DESC LIMIT 10". Do NOT pass natural language — this is sent directly to the PostHog query API.

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The readOnlyHint annotation already covers the safety profile, so the description is not required to restate it. The description adds only that data comes from PostHog and returns a Dict, but does not disclose data freshness, rate limits, or external dependencies. This is adequate but minimal given the annotation coverage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is short and front-loaded with a clear summary followed by a return type. It has no filler, though the return statement adds little beyond what the name implies. Still, it is appropriately concise for a tool whose schema is highly self-descriptive.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, but the description states the return type as a Dict. All parameters are fully documented in the input schema, including the metric enum and the special days=0 behavior. The description is complete enough given the rich schema and readOnly annotation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% with detailed explanations for days, metric, and custom_query. The description itself adds no parameter-level semantics, so the baseline of 3 is appropriate since the schema carries the full burden.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description 'Query the user's website analytics from PostHog' uses a specific verb ('query'), names the resource ('user's website analytics'), and identifies the source ('PostHog'). This clearly differentiates it from other query_* siblings like query_people or query_prospects.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives no guidance on when to choose this tool over alternatives, nor does it mention exclusions or when not to use it. Although the schema hints at metric-specific use cases, the main description lacks any explicit routing or usage context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_companiesQuery CompaniesA
Read-only
Inspect

Columns: id, display_name, domain (normalized bare host), linkedin_company_id, owner_email, data (JSONB), created_at, updated_at. Sliq's CRM is deals and tasks: to see whether a company has a deal or a task, use query_deals (status='all') / query_crm_tasks with company_id. JSONB queries: data->>'some_key' ILIKE '%...%'.

Firmographic columns (captured from company enrichment; blank/NULL until enriched): linkedin_url (bare slug), industry, description, location (HQ), logo_url, employee_count, follower_count, founded_year, company_type, annual_revenue, total_funding, latest_funding_stage.

To list the people you know at a company, take an id from here and call query_people with where_clause="company_profile_id = <id>".

Pass group_by for per-bucket counts over ALL your companies instead of a row list — a whole-set aggregate, never capped at the 200-row limit, so it answers "how many companies per industry/location" without paging. Ignores where_clause. In row mode (group_by omitted) — a dict with count, truncated, and items array (each row {id, display_name, domain, linkedin_company_id, owner_email, linkedin_url, industry, description, location, logo_url, employee_count, follower_count, founded_year, company_type, annual_revenue, total_funding, latest_funding_stage, data, created_at, updated_at}). In aggregate mode (group_by set) — a dict {group_by, groups} where groups is a list of {key, count} ordered by count descending.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax results (default 50, capped at 200).
offsetNoRows to skip for paging (default 0). When the result is truncated, re-call with offset += limit for the next page.
group_byNoAggregate mode, returned instead of the row list — whole-set per-bucket counts over all your companies (not capped by `limit`), bucketed by "industry" or "location". Omit for the row list.
order_byNoSQL ORDER BY (default: created_at DESC).created_at DESC
where_clauseNoSQL WHERE condition (default: all your companies). Examples: "domain = 'stripe.com'", "display_name ILIKE '%acme%'".1=1

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations only provide readOnlyHint=true, so the description carries the burden of explaining behavior. It does so richly: rows accrue from multiple sources, a company may have no linked people yet, group_by ignores where_clause, and aggregate mode is not capped by limit. This goes well beyond the structured annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every section earns its place: purpose, sibling differentiation, column inventory, JSONB example, firmographic fields, and return shape. It is well-structured with front-loaded purpose and no redundant filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, but the description provides a detailed returns section describing both row mode and aggregate mode shapes. Combined with parameter explanations, CRM routing, and column documentation, it gives an agent everything needed to call and interpret results correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, but the description still adds meaningful semantics: group_by ignores where_clause, aggregate mode runs over all companies, and JSONB query syntax is demonstrated. These nuances are not fully present in the input schema and materially help the agent construct correct calls.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource ('Query your canonical companies (`company_profiles`)') and immediately defines the deduped nature of the entity. It clearly differentiates itself from query_monitored_companies, so an agent can pick the right tool without inspecting schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit routing: use query_monitored_companies for the LinkedIn post-monitoring list, query_deals/query_crm_tasks for CRM deal/task questions, and query_people to list people at a company. It also explains when to use group_by mode. This leaves little ambiguity about when this tool should be selected.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_crm_tasksQuery Crm TasksA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
qNoText match on the task title, or the linked contact, company, or deal name.
limitNoMax tasks (default 25, capped at 200).
offsetNoTasks to skip for paging; re-call with offset += limit while has_more is true.
statusNo'open' (default), 'done', or 'all' (open and done).open
deal_idNoOnly tasks on this deal.
person_idNoOnly tasks linked to this person (a query_people `id`).
company_idNoOnly tasks linked to this company (a query_companies `id`).
due_beforeNoOnly tasks due before this day (YYYY-MM-DD). For "overdue" pass today.
assignee_emailNoOnly tasks assigned to this teammate. For "my tasks" pass the user's own email.

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description goes well beyond the readOnlyHint annotation: it discloses that tasks are shared across the user's team, that follow-up suggestions not yet added are excluded, and that the results are sorted. The returns block further reveals that open_task_count and next_task on linked deals are not filled here, which is precise behavioral transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core behavior and use cases, and the returns block is structured and concise. Every sentence adds value, from the sort order to the exclusion of follow-up suggestions. It is detailed but not bloated, earning a 4.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With 9 optional parameters and no output schema, the description compensates by providing a full returns shape and clarifying data semantics (e.g., linked deal fields may be null). The annotations cover read-only safety, while the description covers behavior, sorting, filtering context, and the update workflow. Nothing essential is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

All 9 parameters already have detailed descriptions in the input schema (coverage 100%), including examples like 'For 'my tasks' pass the user's own email' and 'For 'overdue' pass today.' The main description adds context like sorting and team sharing but doesn't add parameter-level meaning beyond what the schema provides. Baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('List') and resource ('CRM tasks'), names the 'Tasks view', and specifies sorting behavior (earliest due first, undated last). It distinguishes from siblings like update_crm_task by explicitly mentioning finding a task's id before updating, and it gives concrete use cases like 'what's on my plate' and 'what's overdue'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says 'Use this for...' with several concrete scenarios and points to update_crm_task as the follow-up. It also notes a limitation (follow-up suggestions not included) that helps agents know when the tool won't apply. However, it doesn't name an alternative for the excluded case, so it stops short of full when-not guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_dealsQuery DealsA
Read-only
Inspect

Every result also carries the pipeline's stages (in order) and a pipeline_summary (open count and amount, closing this month, won/lost counts, win rate) for the whole pipeline — the summary ignores the filters. Dict with paging info, the stages, the pipeline summary, and the deals page. {total, has_more, stages: [{id, name, position}], pipeline_summary: {open_count, open_amount, closing_this_month {count, amount}, won_count, lost_count, win_rate}, deals: [{id, name, amount (decimal string or null), status, stage {id, name, position}, person_profile {id, name, title, email, linkedin_url, ...} or null, company_profile {id, name, domain} or null, assignee_email, expected_close_date, notes, created_at, stage_changed_at, closed_at (when it was won or lost; null while open), archived_at, open_task_count, next_task {id, title, due_at} or null, suggested_task, data}]}.

ParametersJSON Schema
NameRequiredDescriptionDefault
qNoText match on the deal name, company name, or contact name.
limitNoMax deals (default 25, capped at 200).
stageNoOnly deals in this stage, by name (e.g. "Proposal") or id. Open deals sit in a stage; won/lost is the status, not a stage.
offsetNoDeals to skip for paging; re-call with offset += limit while has_more is true.
statusNo'open' (default), 'won', 'lost', or 'all'.open
person_idNoOnly deals linked to this person (a query_people `id`).
company_idNoOnly deals linked to this company (a query_companies `id`).
assignee_emailNoOnly deals owned by this teammate (an active teammate from list_teammates).

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations include readOnlyHint: true, so the tool is already marked as read-only. The description adds valuable context about the shared nature of deals, the inclusion of stages and pipeline summary in every result, and that the summary ignores filters. It also details the return fields in the <returns> section, including complex nested structures. This goes beyond the annotation by clarifying data scope and returned metadata.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with a summary and detailed returns section. It is front-loaded with the main purpose and example use cases, then details the return format. Every sentence adds value, and the use of bullet-style examples is efficient without being verbose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the parameter count (8), the rich schema descriptions, and the absence of an output schema, the description compensates by providing a detailed <returns> section that explains the output structure. It also clarifies edge cases like closed_at null while open and pipeline_summary ignoring filters. The tool is complex, yet the description covers all necessary aspects for correct invocation and result interpretation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already describes each parameter clearly. The description does not add additional semantics beyond what's in the schema, as it focuses on the overall behavior and return structure. However, it helps contextualize parameters like status and stage by explaining their relationship (e.g., 'won/lost is the status, not a stage'). This adds slight value, but is not enough to warrant a 4.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool lists deals in the user's CRM pipeline, newest first, with specific example queries. It also highlights that deals are shared across the team and includes the pipeline summary. This distinguishes it from related tools like query_people, query_companies, and update_deal.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit use cases such as 'what's in my pipeline', 'does Jane have a deal', 'what did we win this month', and 'to find a deal's id before update_deal.' It also explains that the summary ignores filters, which helps avoid misinterpretation. No exclusion of alternatives is needed as it is clear when this tool is appropriate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_linkedin_post_engagementsQuery Linkedin Post EngagementsA
Read-only
Inspect

Use this to find warm leads, identify who engaged with specific posts, or filter engagers by role/company. Chain with query_linkedin_posts when drilling into a specific post, or filter on the post_analytics_id that fetch_post_engagers returns ("post_id = 42").

Columns: id, post_id, engagement_type ('reaction'|'comment'), reaction_value (LIKE|ENTERTAINMENT|EMPATHY|PRAISE|INTEREST|APPRECIATION, empty for comments), author_name, author_provider_id, author_headline, author_profile_url, comment_text, engaged_at, fetched_at, created_at. Dict with count, truncated, and engagements array (each row carries the columns above).

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNodefault 50, max 200.
offsetNoRows to skip for paging (default 0). When the result is truncated, re-call with offset += limit for the next page.
order_byNodefault "engaged_at DESC NULLS LAST" (comments have timestamps, reactions don't).engaged_at DESC NULLS LAST
where_clauseNoSQL WHERE. Examples: "engagement_type = 'comment'" "author_headline ILIKE '%VP%'" "post_id = 42" "engagement_type = 'reaction' AND reaction_value = 'PRAISE'"1=1

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, and the description adds valuable scope context: it covers both the user's own posts and third-party posts fetched via fetch_post_engagers. It also discloses the return payload shape (count, truncated, engagements array). It does not contradict the annotations, though it omits rate-limit or auth details.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with separate summary and returns sections, and it front-loads the most important scoping sentence. The column list is compact and useful, and there is no filler or redundant restating of the tool name.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only query tool with no required parameters, this description covers scope, usage, chaining, column semantics, and return shape. Paging and ordering are documented in the schema, so nothing an agent needs to call this tool correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description adds meaning beyond the schema by explaining that post_id corresponds to post_analytics_id from fetch_post_engagers and by defining reaction_value semantics such as 'empty for comments,' which is not present in the input schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Query individual reactions/comments on tracked LinkedIn posts.' It further distinguishes this tool from query_linkedin_posts by clarifying it returns engagement-level rows, not posts, and it references fetch_post_engagers as the upstream source.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit use cases ('find warm leads, identify who engaged with specific posts, or filter engagers by role/company') and chaining guidance with query_linkedin_posts and fetch_post_engagers. It doesn't explicitly state when not to use this tool in favor of a sibling, but the examples make the intended context clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_linkedin_postsQuery Linkedin PostsA
Read-only
Inspect

Use this to answer "which of my posts performed best?", "what's my average engagement?", "show me posts from last month", etc. This is the account-global table, with no agent/monitor dimension — for the feed a specific agent's monitors discovered (and to act on it), call query_monitored_posts.

Columns: id, social_id, text, share_url, posted_at, impressions, reactions, comments, reposts, is_own_post, author_name, author_headline, fetched_at, created_at, plus a derived engagement_rate string (reactions+comments+reposts over impressions, matching the UI) — '0.0' for posts with no impression data (e.g. third-party posts). The engagement_rate is computed after the query runs, so it can't appear in where_clause or order_by; filter and sort on the raw columns instead. Dict with count, truncated, and posts array (each row carries the columns above plus the derived engagement_rate).

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNodefault 25, max 200.
offsetNoRows to skip for paging (default 0). When the result is truncated, re-call with offset += limit for the next page.
order_byNodefault "posted_at DESC". For top performers: "impressions DESC".posted_at DESC
where_clauseNoSQL WHERE. Examples: "posted_at >= NOW() - INTERVAL '30 days'" "impressions > 1000" "text ILIKE '%hiring%'" "is_own_post = false"1=1

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description explains the data refresh cadence (daily M-F), the derived engagement_rate field that is computed after query and thus cannot be used in where_clause/order_by, and the meaning of is_own_post. It omits rate limits or auth requirements, but those are less critical for a read-only query tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is lengthy but well-structured into summary and returns sections. It front-loads the purpose and packs in necessary details without redundancy. Slightly verbose but justified by the tool's complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a query tool with 4 parameters and no output schema, the description covers the data model, derived field limitations, paging, and sibling differentiation. Nothing essential for correct invocation is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, but the description adds value by explaining the engagement_rate derived field's non-filterability and giving practical where_clause examples. It also clarifies offset paging behavior. This goes beyond the schema's bare parameter definitions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool queries LinkedIn posts tracked by the user, distinguishing own posts from third-party ones. It explicitly names the sibling query_monitored_posts and differentiates by scope, so an agent can tell them apart without inspecting schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides concrete example questions the tool answers and explicitly states when to use the alternative query_monitored_posts for agent-specific monitors. This is direct when/when-not guidance, leaving no ambiguity.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_monitored_companiesQuery Monitored CompaniesA
Read-only
Inspect

mode="search" (default): individual company rows. mode="stats": aggregate counts, with optional group_by for analytics.

Columns: display_name, domain, stage ('active'/'removed'), data (JSONB), agent_id, created_at, updated_at. JSONB queries: data->>'last_sweep_status' = 'error'. For outreach prospects, call query_prospects. In search mode, {count, truncated, items array}. In stats mode, {total, by_stage: {active: N, removed: M}, groups?: {: {total, by_stage}}}.

ParametersJSON Schema
NameRequiredDescriptionDefault
modeNo"search" to list rows, "stats" for aggregate counts.search
limitNoMax results (default 50, capped at 100). Search mode only.
offsetNoRows to skip for paging (default 0, search mode only). When the result is truncated, re-call with offset += limit for the next page.
agent_idNoFilter to a specific agent. Omit for cross-agent queries.
group_byNo(stats mode only) JSONB data key to group by; each group carries the same by_stage map as the top-level stats.
order_byNoSQL ORDER BY (default: created_at DESC). Search mode only.created_at DESC
where_clauseNoSQL WHERE condition (default: all). Examples: "stage = 'active'", "domain ILIKE '%.io'"1=1

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The readOnlyHint annotation already establishes the safety profile, and the description adds useful behavior beyond it: mode-specific return shapes, a truncation signal, the columns list, and a JSONB query example. It does not mention auth or rate limits, but those are less critical for an explicitly read-only query tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and well-structured: modes and scope are front-loaded, followed by return shapes. The columns and JSONB note earn their place because they directly help agents construct valid where_clause and group_by arguments.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description supplies complete return shapes for both modes, including the truncated paging flag. It covers mode selection, grouping, JSONB filtering, pagination-relevant return data, and the prospect alternative, making the tool usable end-to-end.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3 even without extra parameter detail. The description adds a useful JSONB where_clause example and clarifies mode/group_by interplay, but most parameter semantics are already fully documented in the input schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Query an agent's monitored companies.' It clearly distinguishes two modes (search vs stats) and explicitly routes outreach-prospect queries to query_prospects, which separates it from a closely related sibling.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear mode-selection guidance: search for individual rows, stats for aggregate counts, and group_by for analytics. It also explicitly tells agents to use query_prospects for outreach prospects, though it does not contrast with sibling query_companies.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_monitored_postsQuery Monitored PostsA
Read-only
Inspect

mode="search" (default): individual post rows. mode="stats": per-status counts.

Each row is one post a monitor surfaced, with the comment Sliq drafted for it (draft_comment) and the post's lifecycle status: 'discovered' (claimed, pre-draft), 'queued' (drafted + enqueued), 'commented' (comment sent), 'rejected' (user declined the draft), 'skipped' / 'skipped_no_credit' (no comment drafted), 'engagers_only' (captured for its engagers, not commented), 'surfaced' (a surface-only monitor discovered it — no comment, no capture, just surfaced in the feed to act on manually). reaction_status is the independent reaction track: '' (no reaction), 'queued' (a reaction enqueued), 'reacted' (sent), 'rejected' (user declined it), 'skipped' (deduped) — disjoint from status since one post can be both commented and reacted to. reaction_type is which reaction landed (like/celebrate/support/love/insightful/funny, '' until sent).

Columns: id, monitor_id, post_urn, post_url, author_name, author_provider_id, post_text, reaction_count, comment_count, posted_at, status, draft_comment, reaction_status, reaction_type, created_at. Pass a row's id to generate_monitored_post_comment to draft + queue a Sliq comment for it.

Pass agent_id to scope to that agent's monitors — omitting it reads every monitored post across all the user's agents. To act on recent posts (e.g. queue a reaction on this agent's fresh finds), pass agent_id and filter on posted_at; this keeps actions on the agent's own scoped feed rather than the account-wide analytics.

This is the monitor-scoped feed. query_linkedin_posts is a different table — the account-global analytics of the user's own posts plus any post they've fetched engagers for, with no agent/monitor dimension. Use that for "how did my posts perform"; use this for "what did this agent's monitors find". In search mode, {count, truncated, items array}. In stats mode, {total, by_status: {discovered: N, commented: M, ...}}.

ParametersJSON Schema
NameRequiredDescriptionDefault
modeNo"search" to list rows, "stats" for per-status counts.search
limitNoMax results (default 50, capped at 100). Search mode only.
offsetNoRows to skip for paging (default 0, search mode only). When the result is truncated, re-call with offset += limit for the next page.
agent_idNoScope to one agent's monitors. Omit for cross-agent queries.
order_byNoSQL ORDER BY (default: created_at DESC). Search mode only.created_at DESC
where_clauseNoSQL WHERE condition (default: all). Examples: "status = 'commented'", "posted_at >= NOW() - INTERVAL '24 hours'", "author_name ILIKE '%founder%'"1=1

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Even with readOnlyHint=true, the description adds substantial behavioral context: the two modes, the full lifecycle status set (discovered/queued/commented/rejected/skipped/engagers_only/surfaced), the independent reaction_status track, and the consequence of omitting agent_id (reads every monitored post across all agents). It also clarifies that rows carry draft_comment and can be passed to generate_monitored_post_comment, making behavior predictable. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but justified: it is front-loaded with purpose and modes, and the status/reaction enumerations are essential because no output schema exists and the schema's where_clause examples only hint at filterable values. The <summary>/<returns> structure keeps it scannable, and there is no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

This complex 6-parameter, 0-required tool with no output schema is fully covered: return shapes for both modes are stated, the item columns are enumerated, paging behavior is referenced (offset += limit when truncated), and the sibling distinction is explicit. The only details left to the schema are parameter defaults and caps, which is appropriate.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, and the description adds real value on top: it defines the status values usable in where_clause, explains agent_id cross-agent behavior beyond the schema, and clarifies mode output differences. It doesn't add much on limit/offset/order_by, which the schema already documents, so it stops short of a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Query the posts an agent's LinkedIn monitors discovered', and immediately scopes it as 'this agent's own monitor-scoped feed, not the account-global post analytics.' It also names the sibling query_linkedin_posts and explains the exact distinction ('how did my posts perform' vs 'what did this agent's monitors find'), so an agent can select it correctly without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit when-to-use and when-not-to-use guidance: use this tool for 'what did this agent's monitors find' and query_linkedin_posts for 'how did my posts perform'. It also covers mode selection, agent_id scoping, and routing a row's id to generate_monitored_post_comment, which is actionable guidance beyond a simple purpose statement.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_peopleQuery PeopleA
Read-only
Inspect

Columns: id, display_name, title, headline, company, location, summary, linkedin_url, linkedin_provider_id, email, connections_count, follower_count, is_open_profile, is_premium, work_experience (JSONB), education (JSONB), company_profile_id, owner_email, data (JSONB), created_at, updated_at. company_profile_id is the person's canonical employer FK (NULL when unresolved). Sliq's CRM is deals and tasks: to see whether someone has a deal or a task, use query_deals (status='all') / query_crm_tasks with person_id. connections_count / follower_count are NULL when never fetched (not 0); is_open_profile / is_premium are NULL likewise. JSONB queries: data->>'some_key' ILIKE '%...%'.

work_experience and education are the person's full LinkedIn employment and education history — the same arrays enrich_linkedin_profiles returns, already stored on the person from the last profile lookup, so reading them here costs nothing (answer "where did they go to school", "are they an LSU alum", "how long in seat" from these instead of a fresh enrich). Each is a list; an empty list means it was never captured for that person. They arrive in LinkedIn display order (NOT sorted by date). These arrays are large for senior people — a broad limit=200 read that returns them can exceed the 50KB direct-return cap and truncate; for a wide pull, either narrow the where_clause or call this from run_code (nothing truncates there) and print only the fields you need.

To list the outreach prospects tracking a person, take an id from here and call query_prospects with where_clause="person_id = <id>".

Each row also carries segments: the person's segment tags across every segment group they're classified into — [{group_id, group_name, tag}], tag 'No match' where the classifier couldn't place them (empty when no group has classified them). Segments are the LLM-defined people dimensions from the Explore Segments surface (e.g. Seniority -> VP); manage them with the segment tools (list_segment_groups etc.).

Pass group_by for per-bucket counts over ALL your people instead of a row list — a whole-set aggregate, never capped at the 200-row limit, so it answers "how many people per " without paging. outreach_stage buckets each person by their most-advanced lead-funnel stage across every campaign; segment buckets each person by their tag in one segment group (pass that group's id as segment_group_id). Both ignore where_clause. In row mode (group_by omitted) — a dict with count, truncated, and items array (each row {id, display_name, title, headline, company, location, summary, linkedin_url, linkedin_provider_id, email, connections_count, follower_count, is_open_profile, is_premium, work_experience, education, company_profile_id, owner_email, segments: [{group_id, group_name, tag}], data, created_at, updated_at}). In aggregate mode (group_by set) — a dict {group_by, groups} where groups is a list of {key, count} ordered by count descending; for "segment" the keys are tag names, 'No match' for people the classifier couldn't place, and '' for people the group hasn't classified.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax results (default 50, capped at 200).
offsetNoRows to skip for paging (default 0). When the result is truncated, re-call with offset += limit for the next page.
group_byNoAggregate mode, returned instead of the row list — whole-set per-bucket counts over all your people (not capped by `limit`). "title" / "company" / "location" bucket by that column's value; "outreach_stage" by the person's collapsed lead-funnel bucket; "segment" by the person's tag in the `segment_group_id` group. Omit for the row list.
order_byNoSQL ORDER BY (default: created_at DESC).created_at DESC
where_clauseNoSQL WHERE condition (default: all your people). Examples: "display_name ILIKE '%chen%'", "title ILIKE '%VP%'", "company_profile_id = 42", "linkedin_url = 'some-slug'".1=1
segment_group_idNoRequired with group_by="segment" — the segment group (from list_segment_groups) to bucket by. Ignored otherwise.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond readOnlyHint=true: documents the 50KB direct-return cap and truncation behavior with the run_code workaround, NULL-vs-0 semantics for counts/flags, JSONB query syntax, work_experience/education provenance and display-order caveats, and that group_by is an uncapped whole-set aggregate. This is unusually rich behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary is front-loaded with the core purpose before the detailed column list and modes. It is long, but for a 6-param tool with two operating modes most sentences carry load; minor density from the full column enumeration keeps it from a 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers columns, return shapes for both row and aggregate modes (embedded returns block), truncation/limit behavior, and cross-tool joins. Nothing an agent needs to invoke it correctly is missing, despite no formal output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already carries per-parameter meaning (baseline 3). The description adds real value on top by explaining group_by aggregate vs row mode, that segment_group_id is required for group_by="segment", and that both aggregate modes ignore where_clause.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Query your canonical people (`person_profiles`) — one deduped row per real person') and immediately distinguishes the entity from the sibling it could be confused with. An agent can tell it apart from query_prospects (per-owner tracking rows) without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit routing: use this for person-level questions, use query_prospects for outreach progress, query_deals/query_crm_tasks for CRM relations, and query_prospects with where_clause="person_id = <id>" to list tracking rows. Includes concrete example queries ('people who are VPs at fintech companies').

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_prospect_researchQuery Prospect ResearchA
Read-only
Inspect

Call this BEFORE researching a lead to avoid re-doing work (and re-spending credits), or to reference prior findings in a follow-up, or to answer "what did you find about X?". Dict with count, truncated, and items array (each row {id, agent_id, person_linkedin_slug, person_linkedin_pid, person_email, subject_kind, company_domain, display_name, content, url, source, created_at}).

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax findings to return (default 50, newest first).
offsetNoNumber of findings to skip, for paging past `limit`.
personNoA lead handle (linkedin_url / linkedin_provider_id / email) to get just that lead's findings; omit for all research on the agent.
agent_idYesThe agent whose saved research to read.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already mark readOnlyHint=true. The description reinforces the read-only nature and adds useful context: research is already saved, calling avoids re-spending credits, and results may be truncated. It matches the annotation and provides behavioral clarity beyond the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is tightly structured with a summary tag and a returns tag. Every sentence earns its place: purpose, when-to-use, and return shape are all present without fluff or repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Even though there is no output schema, the description explicitly documents the returned dict shape and fields. Combined with full parameter schema coverage and readOnlyHint, an agent has enough context to select, invoke, and interpret the result correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema fully documents agent_id, person, limit, and offset. The description adds no parameter-specific details beyond what the schema already states, which is fine given the baseline for full schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description states a specific verb+resource: 'Read back personalization research already saved for an agent.' It clearly distinguishes from write/research tools like save_prospect_research or query_prospects by emphasizing it retrieves already-saved findings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit when-to-use guidance: 'Call BEFORE researching a lead to avoid re-doing work (and re-spending credits), or to reference prior findings in a follow-up, or to answer what did you find about X?' It does not name alternatives or exclusion conditions, so a half-point is held back.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_prospectsQuery ProspectsA
Read-only
Inspect

mode="search" (default): individual prospect rows. mode="stats": aggregate counts, with optional group_by for analytics.

Columns: id, person_id, display_name, email, linkedin_url, linkedin_provider_id, title, headline, company, location, email_stage, linkedin_stage, priority (True = this prospect's LinkedIn sends jump the queue), current_node_id, entered_at, enact_blocked_at, enact_blocked_reason, data (JSONB), agent_id, created_at, updated_at, linkedin_messages_sent, last_outbound_at. There's no rollup stage column; filter on email_stage or linkedin_stage to isolate a funnel (both non-null — not-started is ''; gate absence with = ''). JSONB queries: data->>'title' ILIKE '%founder%'. For monitored companies, call query_monitored_companies.

id is the prospect's outreach_prospects primary key — returned on every search row and queryable in where_clause. When an event hands you a roster of prospect primary keys (a flow node's on_enter enactment carries them in event['prospect_ids']), resolve the whole roster with id IN (...) and order_by="id", so it queues in upload order.

person_id is the canonical-person FK — the query_people entity this outreach row resolves to. Each row also carries that person's canonical identity line — title, headline, company, location — joined from that query_people row: the deduped, typed, filterable values (the prospect's own data blob may carry a separate per-enrollment copy under data->>'title'/'company'). For the rest of the profile — summary, work history, education — call query_people with id = <person_id>. person_id = <id from query_people> lists every prospect row tracking that person. Use it to cross between the two: query_people returns one deduped row per real person, query_prospects returns the per-owner tracking rows under it. These are your own prospects only — a teammate's rows for the same person need as_teammate.

Each search row also carries lead_bucket, the derived lead-funnel bucket (one of not_started/queued/contacted/engaged/replied/interested/meeting_booked/ not_interested/finished/not_reached/already_connected) — a read-only returned field, not a queryable column. To filter by funnel position, gate on email_stage/linkedin_stage, never on lead_bucket. Each row also carries a read-only removal_reason: for a removed prospect (a 'skipped' stage), why they were taken out of the campaign; '' otherwise or when no reason was recorded.

current_node_id / entered_at are the prospect's Campaign Flow position — the sequence-DAG node they currently rest on and when they arrived. current_node_id is NULL for a prospect on the start node / not yet routed onto any node. The node id is opaque; resolve it to a label and kind — and read the per-node occupancy counts — with get_campaign_flow. Filter on it to list one node's occupants (current_node_id = '<id from get_campaign_flow>', or current_node_id IS NULL for the start node) or to find stalled prospects (entered_at <= '<7 days ago>').

enact_blocked_at / enact_blocked_reason answer "why did this prospect stop?" — non-null means their current node's step could not be performed and won't retry until unblocked (enact_blocked_at IS NOT NULL lists a campaign's stuck prospects). Fix the cause, then clear the block with retry_blocked_enactment.

linkedin_messages_sent / last_outbound_at are the count and latest timestamp of outbound LinkedIn messages sent to the prospect (hand-sent ones included; a connection-request note isn't a message, even though LinkedIn replays it into the chat when they accept). For any follow-up check, gate on linkedin_messages_sent, never on linkedin_stage alone. linkedin_stage = 'messaged' is a flat bucket — it can't tell a prospect who's had only the first message from one who's already had several, so selecting on the stage (even with a last_outbound_at cutoff) re-sends a follow-up that already went out. Gate the exact step: linkedin_stage = 'messaged' AND linkedin_messages_sent = 1 AND last_outbound_at <= '<5 days ago>' is the first follow-up; = 2 with a 7-day cutoff is the second. Both fields reflect synced LinkedIn history, which usually syncs within minutes: reliable for day-grained follow-up checks.

Stage meanings — translate these for the user (say "still being looked up", not "resolving"). email_stage and linkedin_stage share one vocabulary; a rung marked "(LinkedIn)" never appears on email. not_started (blank stage, never enrolled) is reported separately in the stats return, not a stored stage.

  • resolving (LinkedIn) — looking up their profile before anything can send; NOT yet contacted.

  • warming (LinkedIn) — a warm-up flow is engaging their content (comments, reactions) to build familiarity, before the first outreach or between steps; not a send in itself.

  • graduated (LinkedIn) — the warm-up finished; what it triggers next (a connection request, an intro message, or nothing) depends on the flow.

  • pending — scheduled to go out (show the scheduled time if present).

  • sent — the connection request (LinkedIn) or the email has been delivered.

  • connected (LinkedIn) — they accepted the connection request.

  • messaged (LinkedIn) — a follow-up message went out after they connected.

  • replied — they replied (a neutral/unclassified reply). Either channel.

  • interested — they replied with clear interest (a positive reply). Ranks above replied. Either channel.

  • meeting_booked — they agreed to or booked a meeting in their reply. The top rung. Either channel.

  • not_interested — they replied with an explicit decline. Ranks above replied, below the positives — still a replier: include it in any "everyone who replied" count. Either channel.

  • skipped — deliberately not pursued: a manual/agent cancel, or a cross-agent duplicate. Either channel.

  • unreachable (LinkedIn) — a permanent failure: invalid/locked/not-found profile, or they don't accept invites.

  • already_connected (LinkedIn) — already a 1st-degree connection, so the request was a no-op; not pursued by default, but you can message them directly.

  • withdrawn (LinkedIn) — the connection request sat unaccepted past the withdrawal window and was auto-retracted.

Acceptance (LinkedIn connection-request accept rate) does NOT come from the connected count — accepters advance to messaged/replied, so connected undercounts. Read it from get_agent_details (tracking_summary.funnel carries per-channel funnels with the connection acceptance_rate), also surfaced on the per-agent LinkedIn CR acceptance line in your Active Agents context; it matures over ~a week (an accept lands up to ~8h after the click, requests sit sent for days), so a campaign's first week reads low. Who/when — who accepted or replied and when, a week-over-week trend — comes from list_prospect_events, not stats: stats are current positions, the event feed is the history.

Analytics — mode="stats" with group_by a JSONB data key (the per-enrollment data->>'...', not the canonical top-level identity columns) surfaces patterns: group_by="title" (which job titles reply/engage), "company", "source" (which lead sources perform). Each group carries the same per-channel stage maps; everyone who replied is the sum of every reply rung — replied, interested, meeting_booked, not_interested (the classified rungs rank above a neutral reply) — the replied count alone undercounts. Report per-channel — don't mix the email and LinkedIn maps. Acceptance has no per-group breakdown — it reads only off the per-agent CR line above. Surface a pattern proactively when one emerges (e.g. "seed-stage companies reply at ~3× the Series-B rate"), not only when the user asks.

Dedup before acting — to check whether you've already tracked or acted on someone, query by whichever identifier you have (omit agent_id to look across all your agents): where_clause="email='...' OR linkedin_url='...' OR linkedin_provider_id='...'". In search mode, {count, truncated, items array}. In stats mode, {total, by_email_stage: {...}, by_linkedin_stage: {...}, not_started, groups?: {: {total, by_email_stage, by_linkedin_stage}}}. not_started is the count of prospects with neither channel stage populated (never enrolled) — in total but in neither stage map.

ParametersJSON Schema
NameRequiredDescriptionDefault
modeNo"search" to list rows, "stats" for aggregate counts.search
limitNoMax results (default 50, capped at 100). Search mode only.
offsetNoRows to skip for paging (default 0, search mode only). When the result is truncated, re-call with offset += limit for the next page.
agent_idNoFilter to a specific agent. Omit for cross-agent queries.
group_byNo(stats mode only) JSONB data key to group by; each group carries the same per-channel stage maps as the top-level stats.
order_byNoSQL ORDER BY (default: created_at DESC). Search mode only.created_at DESC
as_teammateNoRead a consented teammate's prospects instead of your own — pass their email. Gated on that teammate's conversation-sharing setting; a teammate who hasn't shared is rejected. Any `agent_id` must be one of that teammate's agents. Omit for your own.
where_clauseNoSQL WHERE condition (default: all). Examples: "email_stage = 'replied'", "linkedin_stage = 'connected' AND email_stage = 'sent'", "current_node_id IS NULL", "data->>'title' ILIKE '%founder%'"1=1
include_messagesNoIf False (default), `data.message_sent` is omitted from each row.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With readOnlyHint=true already provided, the description still adds substantial behavioral context: read-only returned fields such as lead_bucket and removal_reason, stage vocabulary, sync timing for LinkedIn history, blocked-enactment semantics, and acceptance-rate caveats. It never contradicts the read-only annotation and materially improves correct interpretation of results.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the summary and mode definitions, but it is far too long and functions more as a domain manual than a concise tool definition. Many sentences repeat stage definitions or provide user-facing translation advice that is not necessary for selecting or invoking the tool correctly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, fully covered input schema, read-only annotation, and lack of a separate output schema, the description is complete. It even documents return shapes for search and stats modes, plus edge cases around truncation, stage maps, and not_started counts.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description goes beyond the schema by explaining operational semantics for where_clause, order_by, group_by, and cross-field filtering patterns, such as resolving a roster with id IN (...) and order_by='id' or gating follow-ups on linkedin_messages_sent rather than linkedin_stage alone.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: querying outreach prospects for campaign progress, with two explicit modes (search and stats). It distinguishes itself from related tools such as query_people, query_monitored_companies, list_prospect_events, and get_campaign_flow, so an agent can identify the correct tool without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit usage guidance for both modes and many alternatives: use list_prospect_events for history, get_agent_details for acceptance rates, query_people for full profiles, and query_monitored_companies for monitored-company data. It also explains when to use filters, dedup checks, and follow-up gates, leaving little ambiguity.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_search_resultsQuery Search ResultsA
Read-only
Inspect

Columns: id, agent_id, entity_type, list_name, source, identifier, display_name, verdict, verdict_reason, data (JSONB), created_at, person_id, company_profile_id. Filter by list with "list_name = 'oil-gas'". source is the discovery tool that surfaced the row ('agent' when no discovery tool did; 'legacy' for rows older than the column). verdict is the curation call recorded on the row with record_search_results — 'qualified', 'rejected', or NULL for a row nobody reviewed — with verdict_reason saying why. Rejected rows are left out unless you pass verdict='rejected' or include_rejected, the same as the user's default view, so a where_clause on verdict alone never reaches them.

person_id / company_profile_id are the canonical person / company FK ids (NULL until the row is linked). A person's profile (title, company) and a company's firmographics (industry, location, employee count, description) — the values the agent's People and Companies tabs show — live on the linked record, not in the row's own data: read them with query_people / query_companies on those ids, and filter on them through the id, e.g. "company_profile_id IN (SELECT id FROM research_app_company_profiles WHERE industry ILIKE '%oil%')". A company row with no company_profile_id is an unidentified company and has none. For a person's LinkedIn degree and warm-intro connectors, or the agent's people counted by list, source, degree or connector, use query_task_people instead.

data has no single schema — its shape follows whichever source wrote the row (it is the agent-authored blob). Most person rows nest under person (data->'person'->>'position', data->'person'->'company'->>'name') and most company rows under company (data->'company'->>'name'), but other sources write those fields flat. A path that doesn't exist yields NULL rather than an error, and a NULL comparison drops the row — so a filter aimed at the wrong shape comes back empty or short and reads as a genuine miss. Read a page of rows without a data filter first, then filter against the shape you see.

Person rows are annotated with the team connection overlay (mirrors the Find UI): a matched row's data gains connection_status: {you: bool, teammates: [{email, name, first_name}]} naming the viewer/teammates who are 1st-degree to that prospect; unmatched rows and company rows carry no such key. A top-level connections_coverage: {you: 'none'|'partial'|'csv_uploaded', team_missing: int} reports the owner's own connection coverage and how many active teammates still lack a CSV upload. The viewer is the agent owner. In row mode (group_by omitted) — a dict with count, truncated, and items array (each row {id, agent_id, entity_type, list_name, source, identifier, display_name, verdict, verdict_reason, data, person_id, company_profile_id, columns, created_at}), plus connections_coverage {you, team_missing}. columns is the normalized [label, value] projection of the row's agent-researched data.columns. In aggregate mode (group_by set) — a dict {group_by, groups} where groups is a list of {key, count} ordered by count descending.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum results (default 50, max 200).
offsetNoRows to skip for paging (default 0). When the result is truncated, re-call with offset += limit for the next page.
verdictNoOnly the rows with this verdict, 'qualified' or 'rejected' ('rejected' reads the rows the user's default view filters out). Omit for every verdict.
agent_idYesFilter to a specific agent (required).
group_byNoAggregate mode, returned instead of the row list — per-bucket counts over the whole agent (not truncated by the row cap). "list" buckets rows by list_name; "company" buckets people by employer; "source" buckets rows by the discovery tool that surfaced them. Omit for the row list.
order_byNoSQL ORDER BY (default: created_at DESC).created_at DESC
where_clauseNoSQL WHERE condition (default returns all rows for the agent). Examples: "data->'person'->>'position' ILIKE '%VP%'", "entity_type = 'person'", "list_name = 'oil-gas-operators'".1=1
include_rejectedNoAlso return (and count) the rows marked rejected. Default False.

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=true, and the description goes well beyond that: it discloses that rejected rows are filtered by default, that a NULL path comparison silently drops rows and reads as a genuine miss, that person rows carry a team connection overlay plus connections_coverage metadata, and that data has no fixed schema. These are non-obvious behaviors an agent could not infer from the structured fields.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded summary sentence, then columns, then behavioral caveats and routing — well ordered. It is long and the column enumeration plus overlay explanation is dense, but nearly every sentence carries information an agent needs to avoid empty/wrong results; only mild trimming would be possible.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an 8-parameter query tool with no output schema, the description supplies the return shape itself (row mode vs aggregate mode, columns projection, connections_coverage), the pitfalls, and the alternative tools. Nothing essential to correct invocation appears missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% (baseline 3), but the description adds real semantics: verdict values are the curation call from record_search_results, rejected rows need include_rejected to surface, where_clause must reference the row's columns/FKs (with a subquery example against research_app_company_profiles), and data->'person'/'company' nesting varies by source. That is meaningfully more than the schema provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource and scope: query the agent's entity row store `agent_search_results` (people and companies from discovery, CRM, enrichment, pasted lists). It names the sibling tools it is not (query_people, query_companies, query_task_people), so an agent can route without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit routing rules: profile/firmographic values live on linked records so read them via query_people/query_companies; for LinkedIn degree, warm-intro connectors, or counts by list/source/degree/connector use query_task_people instead. It also states the default-view behavior (rejected rows hidden unless verdict='rejected' or include_rejected) and the recommended workflow (read a page without a data filter first).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_segment_peopleQuery Segment PeopleA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoPage size (default 200).
offsetNoPage offset (default 0).
tag_idNoNarrow to people carrying this tag; omit for all classified people.
channelNo'linkedin' or 'email' to read the outcome from; omit for both.
sendersNoNarrow to people reached by these teammates (emails from a group's `senders`), and read their outcome from those teammates' prospects only. Omit for every teammate; an email not in the workspace is dropped, and if none remain the result is empty.
group_idYesThe segment group to read (from list_segment_groups).
untaggedNoNarrow to the 'No match' people (classified as nothing, empty `tags`); takes precedence over `tag_id`. Omit for all classified people.
agent_idsNoScope to people with a prospect in one or more campaigns (agent_tasks ids); omit for all.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, it discloses newest-classification-first ordering, OR-ed outcome semantics across outreach, empty tags for 'No match' people, and total-before-paging behavior. These details add meaningful behavioral context and do not contradict the annotation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary is front-loaded with the core purpose, then organized into filter guidance, pagination, and a dedicated returns block. Each section earns its place even with an 8-parameter tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, but the returns block supplies a full field-level shape and explains the canonical person identity and outcome OR-ing. Combined with fully described schema parameters, the definition gives an agent enough to select and invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description adds extra meaning by explaining the pagination tail reachability and framing filters as user-selectable drill-downs, though many parameter meanings are already well documented in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with 'The per-person drill-down for a segment group', naming a specific operation and resource. It further distinguishes itself from segment-level tools by specifying that it returns classified people, their tags, identity, and OR-ed outcome flags.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The summary gives concrete filter guidance for tag_id, untagged, agent_ids, senders, and pagination, including the total-before-paging caveat. It does not name alternative sibling tools or state when not to use this tool, so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_tagged_messagesQuery Tagged MessagesA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoPage size (default 200).
offsetNoPage offset (default 0).
tag_idNoNarrow to messages carrying this tag; omit for all classified messages.
channelNo'linkedin' or 'email' to bound the population; omit for both.
sendersNoIn a shared workspace, the teammate email addresses whose messages to return. Omit to return every sender's. Any email not in the workspace is dropped; if that leaves no valid teammate, the result is empty — it does NOT fall back to the whole workspace.
group_idYesThe tag group to read (from list_message_tag_groups).
untaggedNoNarrow to the 'No match' messages (classified as nothing, empty `tags`); takes precedence over `tag_id`. Omit for all classified messages.
agent_idsNoScope to one or more campaigns (agent_tasks ids from a group's `campaigns`); omit for all campaigns. Campaign membership is the prospect's current one.
positionsNoThe conversation positions to return — 'first' (each conversation's first message), 'follow_up' (later messages sent before the prospect replied), and/or 'reply' (messages sent after the prospect replied); omit for all.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=true; the description carries substantial behavioral detail beyond that: newest-first ordering, total before paging, position derivation, outcome flags reflecting the prospect's funnel stage rather than the message's effect, and source/source_id purpose. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but front-loaded with a one-sentence purpose, then filters, then a return-shape breakdown. Some redundancy exists between the summary and the returns block, but the complexity of 9 parameters and a rich response justifies the length.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description fully explains the return structure, per-field meanings, nullability, edge cases like nameless prospects, and paging behavior. For a 9-parameter read-only tool, this is unusually complete and leaves little to guess.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3, but the description adds real value: untagged takes precedence over tag_id, agent_ids reflects current campaign membership, positions semantics are spelled out, and offset/total behavior ensures the whole tail is reachable. This goes beyond the schema's field-level text.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource — 'per-message drill-down for a group' — and enumerates exactly what each message carries: matched tags, recipient, text, and outcome flags. This clearly sets it apart from sibling aggregate tools like get_message_tag_rates or get_message_tag_cross_tab.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It clearly frames when to use it (per-message drill-down for a group) and documents filter combinations like tag_id, untagged, agent_ids, and positions. It does not explicitly name sibling alternatives or state when not to use it, but the 'per-message drill-down' phrasing provides enough contextual differentiation.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_task_peopleQuery Task PeopleA
Read-only
Inspect

Each row: id (the person id query_people uses), display_name, spine (title, company, location, headline), linkedin_url, linkedin_provider_id, email, and:

  • degree: the user's LinkedIn degree to the person, '1', '2', '3' or 'out_of_network'; null when unknown. A person the user is connected to reads '1'.

  • intro: the warm-intro check. state is one of not_checked, checking, queued, found, cold, connected, unresolved. connectors lists the user's 1st-degree connections who know the person (name, headline, identifier, provider_id). mutual_connections_truncated true means connectors is a sample of a longer list. shared_connections_count is LinkedIn's total. result_id is the search row the check read, null when the person has none.

  • outreach_stage: the person's lead-funnel bucket on this agent, '' when not in outreach here. prospect_id, email_stage and linkedin_stage are their prospect row on this agent, null when not enrolled. removal_reason says why they were removed from the campaign, '' when they weren't or no reason was recorded.

  • lists and sources: the search lists and discovery tools that surfaced the person. columns holds the researched [label, value] pairs.

  • verdict, verdict_reason and rejected_on: the curation recorded on the person's search rows, as the Verdict column shows it. 'rejected' when every row is rejected, 'qualified' when any row is qualified, else null, with the reason from the latest row carrying it; on a group_by='list' bucket page, the verdict on that list's row. rejected_on is {lists, reason}: the lists whose own verdict is 'rejected' while the person's verdict isn't, with the latest rejected row's reason; its lists is empty otherwise and on a group_by='list' bucket page. A person rejected on every row is left out unless a "verdict" filter asks for 'rejected' or include_rejected is set — the same as the user's default view — though a tracked prospect always shows.

  • criteria, only with include_criteria: why the person matched, as their opened row shows it. Per list they're in, {list_name, evaluations}; each evaluation is a criterion with the reasoning, a verdict (satisfied 'yes', 'no' or 'unclear', or a numeric score where 7 and up is met) and http(s) references. Criteria run about 2KB per person, so ask for them on a narrow read (a q, one bucket, or a small limit) or from run_code. A list where all of the person's rows are rejected is left out unless the read includes rejected people.

To count people per group, pass group_by alone. To list one group's people, pass group_by plus a bucket key taken from those counts. Otherwise you get everyone, sorted by name. Page until next_cursor is null by passing it back as cursor. A direct call returns 25 rows by default; for a wide pull, call this from run_code with limit up to 200 (nothing truncates there) and print only the fields you need. A dict. A group_by without a bucket returns only group_counts, a list of {key, count} with the largest group first and the '' group last ("degree" keeps closest-first order; "connector" entries add label). Every other call returns results (rows shaped as above) and next_cursor, plus total (every matching person) when group_by is omitted. Every call also returns filtered_out_count, the number of rows a read filtered on verdict 'rejected' alone returns across the whole agent, whatever q and filters: people, or with group_by="list" person-list pairs, so a person rejected on two lists counts twice.

ParametersJSON Schema
NameRequiredDescriptionDefault
qNoA substring matched against name, title, headline and company.
limitNoRows per page (default 25, max 200).
bucketNoOne key from `group_counts` for the same `group_by`; returns that group's people. Needs `group_by`.
cursorNoThe `next_cursor` from the previous page; omit for the first page.
filtersNoClauses that must all match. For "degree", "outreach_stage", "list" or "source", use {"column_key": <dimension>, "operator": "is_any_of", "values": [keys]} with keys as group_counts returns them ('' matches no value). For "segment", use the same shape with the segment keys above. For "verdict", use the same shape with 'rejected' (the people the user's default view filters out) and/or 'qualified'; it matches each person's `verdict`, or with group_by="list" their verdict on each list, and 'rejected' brings the rejected people in without `include_rejected`. For "name", "title", "headline", "company" or "location", use {"column_key": <column>, "operator": "contains", "text": <substring>}.
agent_idYesThe agent whose People tab to read.
group_byNoThe dimension to count or to pick a bucket from. "degree" keys are '1', '2', '3', 'out_of_network'; "connector" keys are a connector's provider_id (else their identifier or name) and carry the connector's name as `label`; "outreach_stage" keys are funnel buckets; "list" and "source" keys are list names and discovery tools; "segment" keys are tag names in `segment_group_id`'s group, plus 'No match' for people the classifier couldn't place; "title", "company" and "location" key on the person's own value. A '' key is the group with no value (for "segment", people the group hasn't classified). A person with several lists, sources, connectors or tags counts in each.
include_criteriaNoAlso return each row's `criteria`. Default False.
include_rejectedNoAlso return (and count) the people left out as rejected. Default False.
segment_group_idNoThe segment group (from list_segment_groups or get_segment_funnel) a "segment" filter or group_by reads. With it, each row also carries `segment`, its tag names in that group.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With only readOnlyHint=true available from annotations, the description carries the behavioral load and does so richly: default 25 rows, limit up to 200 with 'nothing truncates' in run_code, page-until-next_cursor-null semantics, the ~2KB per-person cost of criteria, and the rejection-filtering rule that matches the user's default view (with tracked prospects always shown). This is far beyond what the annotation states.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Dense but front-loaded and well organized: the summary states the resource, the field list explains each row key, and usage/pagination guidance closes it out. Given 10 parameters and a non-trivial output shape, the length is largely earned, though the row-field enumeration is verbose enough to slow scanning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no formal output schema, so the description must document returns, and it does so thoroughly via the <returns> block (group_counts vs results/next_cursor/total, and filter_counts semantics) alongside the per-row field breakdown. An agent has everything needed to call and interpret this tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3, but the description adds genuine meaning the schema does not: the group_by/bucket dependency, the cost warning on include_criteria, the escaped '' key semantics, and the run_code-wide-pull advice. It stops short of restating every parameter's shape, but the supplemental semantics are real.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise verb+resource ('Read one agent's People tab: the people its searches found plus the prospects it tracks, one row per person') and explicitly claims a unique capability ('the only read that carries each person's LinkedIn degree and warm-intro connectors'). This lets an agent distinguish it from siblings like query_people, query_prospects and query_segment_people without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives concrete usage context ('use it for questions about who is 1st-degree, who can introduce the user to someone, how many people are in each list') and operational guidance for group_by/bucket and pagination. It does not name an explicit alternative to use instead, so the routing is implied rather than stated, keeping this below a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

queue_linkedin_post_engagementQueue Linkedin Post EngagementA
Destructive
Inspect

Exactly one of comment_text or reaction_type must be provided.

For a comment, pass comment_text (the text to post). For a reaction, pass reaction_type (one of 'like', 'celebrate', 'support', 'love', 'insightful', 'funny'). The agent picks reaction_type='like' by default for most posts; the user can change it on the approval card.

Resolves the post's social_id (URN format) from the URL, then creates a linkedin_invite_queue item with action_type='comment' or 'reaction' depending on which body field was set. The pre_save signal resolves the per-subtype approval policy — gate (pending_approval) or auto-send (pending). A queued-status dict in one of three forms. {'queued': True, 'message': str} on success {'queued': False, 'deduped': True, 'message': str} when this post already has a live engagement of the same kind (report the message; not an error) {'queued': False, 'error': str} on resolution failure

ParametersJSON Schema
NameRequiredDescriptionDefault
post_urlYesLinkedIn post URL (e.g. 'https://linkedin.com/posts/...-activity-123-...')
post_textNoExcerpt of the post text (for the approval card display)
as_teammateNoQueue the engagement on a consented teammate's account instead of your own — pass their email. It lands as a standalone (account-level) engagement on their queue, not tied to any of your agents. Gated on that teammate's act-on-behalf setting; a teammate who hasn't granted it is rejected. Omit for your own.
author_nameNoName of the post author (for the approval card display)
comment_textNoThe comment text to post. Mutually exclusive with reaction_type.
reaction_typeNoOne of 'like', 'celebrate', 'support', 'love', 'insightful', 'funny'. Mutually exclusive with comment_text.
prospect_identifierNoThe warming prospect's tracking-item identifier (LinkedIn URL/slug) — links this engagement back to the prospect for the system-maintained `data.comments_posted` (comments) or `data.reactions_posted` (reactions) counter. Required for warm-up flow; safe to omit for one-off engagements unconnected to a warm-up campaign.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint=false/destructiveHint=true annotations, it discloses the pending_approval vs auto-send flow, the possibility of automatic posting, and the deduped outcome as a non-error. This materially informs the agent that the call mutates state and that the response should drive follow-up communication.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary is front-loaded with the core contract and approval behavior, and the returns block compactly defines the three possible outcomes. It is longer than minimal, but the added detail about internal resolution and queue creation earns its place for a mutating tool with no output schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is self-sufficient: it explains the approval policy, the one-of rule, the resolution/dedup behavior, and enumerates all three return shapes despite the absence of an output schema. With the schema covering all parameter details, no significant gap remains for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Input schema coverage is 100%, so the baseline is 3. The description adds meaning beyond the schema by enforcing the mutual-exclusivity contract, enumerating accepted reaction types, and providing a sensible default that an agent needs in order to choose correctly.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific action and object: 'Queue an engagement (comment or reaction) on a LinkedIn post.' The verb 'queue' differentiates it from sibling tools that query, manage, or generate LinkedIn content, and the first sentence immediately narrows the scope to comment or reaction.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete invocation rules: exactly one of comment_text or reaction_type, a default reaction_type='like', and an explicit instruction to relay the returned message rather than telling the user it will be reviewed. It provides clear context for when to call it, though it does not name sibling alternatives for querying or managing the queue.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_attachmentRead AttachmentA
Read-only
Inspect

Read a slice of any file in the user's attachment library, by id.

Use this when the attachment summary doesn't contain enough data to answer. For spreadsheets, paginate with row_start/row_end (default window = 500 rows). For PDFs/PPTX and text files, use page_start/page_end (1-indexed, inclusive; default window = 10 pages). When the result has truncated=True, advance page_start to read on.

ParametersJSON Schema
NameRequiredDescriptionDefault
sheetNofor multi-sheet xlsx, the sheet name (default: first sheet)
columnsNolist of column names to keep (default: all)
row_endNozero-indexed, exclusive (default: row_start + 500)
page_endNo1-indexed, inclusive (PDFs/PPTX/text files)
row_startNozero-indexed, inclusive
page_startNo1-indexed, inclusive (PDFs/PPTX/text files)
attachment_idYesa file id from list_attachments or an [Attachment: id=N] summary

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=true; the description adds default window sizes (500 rows, 10 pages), index conventions (zero- vs one-indexed), and the truncated=True continuation behavior. It gives no details on result content or errors, but that is a minor gap given the read-only safety profile.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Every sentence earns its place: purpose, trigger condition, file-type-specific pagination rules, and truncation handling. It is front-loaded with the core action and avoids restating schema field descriptions.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 7 parameters, no output schema, and only readOnlyHint, the description covers the essential use cases and pagination behavior. Missing a precise description of the returned content format is a gap, but the guidance around truncated=True and parameter defaults makes the tool callable without further inference.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, and the description adds meaningful grouping: it tells agents which parameter family applies to which file type and what the default windows are. It also reveals the truncated flag, which informs how the agent should set page_start next, going beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('read') and resource ('any file in the user's attachment library') with a slicing mechanism ('by id'). The phrase 'When the result has truncated=True, advance page_start' reinforces that it is a paginated read operation, and 'any file' helps separate it from email-specific attachment readers.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says to use it when the attachment summary doesn't contain enough data to answer. It also provides concrete pagination instructions (row_start/row_end for spreadsheets, page_start/page_end for PDFs/PPTX/text) and a continuation rule for truncated results. It doesn't name alternative tools or state when not to use it, but the context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_email_attachmentRead Email AttachmentA
Read-only
Inspect

Use this to read the contents of an attached document (e.g. "analyze this spreadsheet", "summarize this PDF"). Parseable types (.csv, .tsv, .xlsx, .pdf, .pptx, .txt, .md) come back readable — pull row/page slices with read_attachment(attachment_id). Any other type is saved to the user's library but isn't readable; use get_email_attachment instead if you only need to fetch or transfer the file.

Provide email_data_id + attachment_id (from search_emails results). Dict with attachment_id (pass to read_attachment), filename, mimeType, summary.

ParametersJSON Schema
NameRequiredDescriptionDefault
attachment_idNoThe attachmentId from the attachments metadata
email_data_idNoID of the email_data record (from search_emails)

TDQS

A3.9/5.0
Behavior1/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations declare readOnlyHint=true, but the description describes write-like behavior: it says the tool will 'persist it as a chat attachment' and that non-parseable types are 'saved to the user's library'. This directly contradicts the read-only annotation, so the description does not align with the structured metadata.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured, front-loads the core behavior, and every sentence earns its place. It covers use cases, supported file types, the alternative tool, parameter provenance, and return value shape without unnecessary filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Even though there is no output schema, the description specifies the return dict keys and how to continue with read_attachment. It also enumerates parseable types and gives fallback guidance, making the tool fully actionable for an agent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description adds a note that users should provide email_data_id and attachment_id from search_emails results, which mostly reinforces what the schema already says rather than adding substantial new meaning.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action: fetch an email attachment, persist it as a chat attachment, and return a handle. It clearly distinguishes this from get_email_attachment and read_attachment by naming them and explaining what each is for.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly says when to use this tool: to read attached documents such as spreadsheets or PDFs. It also gives a clear exclusion rule for non-parseable types and directs users to get_email_attachment when only fetching or transferring the file is needed.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

record_search_resultsRecord Search ResultsAInspect

Dedup per (agent, identifier, list_name). Existing rows: data['signals'] accumulates across runs (union by URL); other fields are last-writer-wins. Unset fields preserve existing values. Cross-list duplicates of the same identifier are allowed (intentional — two lists may track the same entity for different reasons).

Identifiers are stored canonical — the registrable domain (eTLD+1) for companies (https://www.Acme.com/ and news.acme.com both → acme.com), bare slug for LinkedIn (https://linkedin.com/in/JohnDoe/ → johndoe). Match the canonical form when querying via query_search_results.

Empty-list semantics: signals: [] preserves accumulated signals (the cross-run contract); evaluations: [] REPLACES (LLM-rewritten per run).

Put every user-requested display attribute — a metric, a person's email, any column the user asked to see — in data.columns as {label: value}, one entry per column under the label the user asked for. columns merges by key-union across runs (new values win). The Output tab and CSV render these as columns; a value written to a top-level extra key instead is not surfaced.

Set each result's source to the discovery tool that surfaced that candidate — per row, so a batch merged from several tools keeps each row's origin. Leave it at 'agent' only for rows no discovery tool surfaced.

This is also how you filter what you screened: give each screened row a verdict — 'qualified' for one you keep, 'rejected' with a verdict_reason for one you drop — under the identifier and list_name it was saved with, so the verdict lands on that row. To mark a row already saved, re-record it with its identifier, display_name and list_name plus the verdict; the data you leave out stays as stored. A rejected row stays saved but drops out of the list the user sees by default; the table counts what you filtered out, and the user reviews those rows, with their reasons, by filtering it on Verdict. query_search_results / query_task_people leave it out the same way unless you ask for rejected rows. A row recorded without a verdict keeps whatever verdict it already has. {created, updated, total}.

ParametersJSON Schema
NameRequiredDescriptionDefault
resultsYesThe people/company rows to upsert; each SearchResult carries its identifier, display_name, source, any signals/columns to store, and optionally its verdict and verdict_reason.
agent_idYesThe agent these rows belong to.
list_nameYesshort kebab slug naming the bucket these rows belong to (e.g. 'oil-gas-operators', 'fintech', 'companies'). Required. Distinct slugs are separate lists on the agent's People and Companies tabs; reuse the same slug across calls to accumulate into one list. For uncategorized runs, pick a single descriptive slug (e.g. the entity_type plural — 'companies' / 'people') and use it consistently.
entity_typeNoWhether `results` are companies or people ('company' by default).company

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=false and destructiveHint=false; the description supplies far more: per-(agent, identifier, list_name) dedup, signals union-by-URL accumulation, last-writer-wins fields, unset fields preserved, cross-list duplicates allowed, canonical identifier storage (eTLD+1, LinkedIn slug), and the asymmetry between `signals: []` (preserve) and `evaluations: []` (replace). These are exactly the non-obvious write semantics an agent needs and none are in the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is front-loaded with a `<summary>` block that leads with the core verb+resource, and each subsequent sentence carries distinct operational detail rather than repetition. It is long, and a few clauses (e.g. the cross-list duplicate rationale) sit near the edge of earning their space, but almost nothing is pure filler given the tool's complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, but the `<returns>` block documents `{created, updated, total}`. For a mutation tool with nested payloads, complex merge semantics, and screening/verdict behavior, the description covers the full decision surface an agent needs: identifiers, columns, signals, verdicts, source attribution, and list bucketing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so a baseline of 3 would be acceptable, but the description adds real semantic content beyond the schema: it explains the columns merge-by-key-union behavior and why top-level extra keys are not surfaced, how `source` should be set per row, how to mark an already-saved row by re-recording identifier+display_name+list_name+verdict, and how rejected rows are hidden but retained. That is genuine parameter meaning the schema descriptions alone do not fully convey.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: 'Upsert people/company rows into the agent's entity row store (`agent_search_results`)'. It explicitly names the sibling discovery tools (exa_find_people, search_linkedin_people, fetch_post_engagers) and explains what distinguishes this tool from them, so an agent can disambiguate without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use conditions ('a bulk set of people or companies you assembled yourself: a user-pasted roster, a CRM read, or web-search results you resolved') and a when-to-still-use-it-anyway clause versus the discovery tools that auto-save ('a curated or differently-shaped view — a filtered subset, added columns, or people nested under company rows'). It also covers the screening/filtering workflow via verdict. This is close to exhaustive routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

register_manual_linkedin_invitesRegister Manual Linkedin InvitesAInspect

Only registers invites still pending; anything else comes back as not_pending — which can mean already accepted or never sent, so don't assume acceptance (check the prospect's linkedin_stage before messaging). Unmatched prospects that have a LinkedIn URL but no provider_id yet come back under resolving: Sliq looks up their profile in the background to identify them (in progress, not failed). Attach a resolved_profile trigger to act on each result — an already-connected one sends no acceptance event, so it won't engage on its own. Use this only after the prospects are tracked and the user has confirmed they sent the requests themselves.

Usually returns in seconds: the pending-invitations read stops as soon as every named prospect is matched, and recently hand-sent invites sit at the top of the list. When some prospects aren't found there, it pages the user's full backlog with paced gaps to protect their LinkedIn account — up to a couple of minutes on a large backlog — so warn the user about the possible wait only when registering invites sent long ago or likely already accepted, not for a just-sent batch.

Pass the specific people the user named in identifiers. Only set all_tracked=True when the user said they hand-invited the whole campaign — it registers every tracked prospect that has a real pending invitation, which can pull in an unrelated pending invite, so it's a deliberate opt-in rather than the default. Dict with registered / already_registered / not_pending / resolving / ambiguous / skipped lists, agent_has_accept_trigger (whether the agent has an accept trigger — a sequence campaign advances on accept via its flow regardless), and a summary message.

ParametersJSON Schema
NameRequiredDescriptionDefault
node_idNoOptional. The sequence-DAG connection_request node the hand-sent CR satisfies (mirrors setup_linkedin_sequence's node_id) — stamped on the recorded row so its acceptance advances the flow onto the next node. Omit to auto-detect the agent's sole CR node; pass it explicitly on a multi-CR sequence. Rejected with ModelRetry if it isn't a connection_request node in the agent's sequence.
agent_idYesThe agent whose tracked prospects were manually invited.
all_trackedNoRegister every tracked prospect in the agent — use only when the user invited the whole campaign by hand.
identifiersNoLinkedIn URLs, provider_ids, or names of the people the user invited by hand. Required unless `all_tracked` is set.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations only say readOnlyHint=false and destructiveHint=false; the description adds rich behavioral context: it validates against real pending invitations, can return not_pending for accepted-or-never-sent invites, performs background resolution for unmatched prospects, paces backlog reads to protect the account, and may take minutes on large backlogs. This substantially exceeds what annotations convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every block earns its place: core behavior, edge-case semantics, latency expectations, and parameter usage each get their own focused segment. It is front-loaded with the main purpose and uses a <returns> block for output instead of burying it in prose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutating tool with four params, no output schema, and several edge cases, the description covers return categories, latency, non-pending semantics, background resolution, and acceptance-trigger behavior. No key decision an agent needs to invoke it safely is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description adds meaning by clarifying that identifiers should be 'the specific people the user named' and that all_tracked=True is a deliberate opt-in that can pull in unrelated pending invites. It doesn't add param semantics for node_id beyond the schema, but the extra context for the other parameters justifies a 4.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and object: 'Register connection requests the user sent by hand so Sliq runs the accept→message sequence for them.' It further differentiates the operation by describing verification against LinkedIn's pending invitations, provider_id recovery, and cancellation of the redundant queued request, which separates it from queue-management siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit when-to-use conditions ('Use this only after the prospects are tracked and the user has confirmed they sent the requests themselves') and a clear when-not/all_tracked caveat. It does not name an alternative sibling tool, so an agent must infer how this differs from related queue-management tools; still, the conditions are concrete enough to select it correctly.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

remove_triggerRemove TriggerAInspect

Removing a flow node's on_enter enactment un-arms that node — prospects entering it no longer enact until you re-arm it with add_trigger. To stop it firing while keeping its authored content, pause it with update_trigger(status='paused') instead. Dict with success, agent_id, and next_run_at. Read the agent's surviving triggers via get_agent_details.

ParametersJSON Schema
NameRequiredDescriptionDefault
trigger_idYesID of the trigger to remove (from the agent's trigger list).

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the sparse annotations, the description discloses meaningful behavior: flow-node `on_enter` enactments un-arm, prospects stop enacting, re-arming is possible via `add_trigger`, and pausing is the non-destructive alternative. This adds substantial context not available from annotations alone and does not contradict them.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact, well-structured with summary and returns sections, and every sentence earns its place. Behavioral nuances and the pause alternative are front-loaded without unnecessary filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description still explains the return dict contents (`success`, `agent_id`, `next_run_at`) and points to `get_agent_details` for reading surviving triggers. Edge cases like removing the last trigger are covered, making it complete for the operation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, and the schema already describes `trigger_id` as 'ID of the trigger to remove (from the agent's trigger list).' The description only says 'by id,' adding no substantive meaning beyond what the schema provides, so the baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Delete one trigger from its agent by id.' It clearly distinguishes itself from siblings by contrasting with `add_trigger` and `update_trigger`, so an agent can tell which operation to invoke.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit usage context: the agent keeps running on remaining triggers, and removing the last trigger stops future firing. It also names an alternative condition directly: to stop firing while keeping content, use `update_trigger(status='paused')` instead.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

report_enactment_blockedReport Enactment BlockedAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
reasonYesShort machine-friendly cause, e.g. 'linkedin_disconnected', 'unreachable_profile', 'no_valid_recipient'.
node_idYesThe flow node whose step could not be performed — must be the node the prospect currently rests on.
agent_idYesID of the agent whose flow the prospect is on.
prospect_idYesThe prospect's `id` — the outreach_prospects primary key, as returned on every query_prospects row and carried on flow on_enter events (event['prospect_ids']).

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses the key side effect beyond annotations: marking the prospect blocked on the current node and preventing re-dispatch, with clearing behavior on any later node move. Since readOnlyHint=false already signals a write, this adds meaningful context about exactly what changes and how it is reversed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with a summary paragraph and a returns element, and the core purpose is front-loaded. It is a bit wordy in places ('retrying identically would fail identically') but every sentence contributes meaningful guidance.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given this is a side-effectful reporting tool with no output schema, the description covers the essential context: when to call it, what side effect it triggers, how the block is cleared, required parameter constraints, and a brief return description. An agent has enough information to invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all four parameters thoroughly. The description reinforces the meaning of node_id by tying it to the prospect's current node and gives reason examples, but it does not add substantial parameter-level semantic detail beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific action ('report that a flow step could not be performed'), the affected resource (a prospect on a flow node), and the concrete mechanism (marks prospect blocked, stops re-dispatching). It also distinguishes itself from retry_blocked_enactment by noting retrying identically would fail identically.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states explicit conditions for use ('LinkedIn disconnected, unreachable profile, no valid recipient'), provides an explicit directive ('Call this for every prospect you cannot enqueue or route... never silently skip one'), and clarifies the block lifecycle. It does not explicitly name alternative tools, but the sibling retry_blocked_enactment is clearly implied by the contrast.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

request_user_actionRequest User ActionAInspect

Prefer on-platform tools when the channel is automatable: setup_linkedin_sequence for LinkedIn DMs and connection requests, the email tools (draft_email, setup_email_sequence) for email outreach, find_email for contact lookups. Use this tool only after ruling those out.

This tool is for a one-off ask about a particular prospect. When the off-platform step is a step of a sequenced campaign — every prospect reaching that point gets it, and the campaign continues once it's done — author a manual node in define_sequence instead: entry queues the same row, and marking it done advances the prospect onto the node's next step.

Dedup is per pending row on (agent, prospect, bucket_name): if the same prospect already has a pending row for the same bucket the tool returns created=False with the existing row's bucket and action_description so you can see what's already queued. Re-flagging the same prospect for a different bucket is allowed; re-flagging after the user marks a prior row done or dismisses it is also allowed. Dict with created (bool), action_id (int or None), id (int), and reason (str).

ParametersJSON Schema
NameRequiredDescriptionDefault
agent_idYesID of the campaign agent this request belongs to.
bucket_nameYesShort user-facing label naming the ask (e.g. "voice-note", "founder-gift"). Shown in the Slack DM and home-page row. Also the third dimension of the pending-dedup key — two different buckets can coexist as pending rows on the same prospect.
prospect_idYesThe prospect's `id` — the outreach_prospects primary key, as returned on every query_prospects row.
action_descriptionYesOne-line description of what the user should do.
completion_criteriaNoHow the agent should advance the prospect after the user sends an update about what they did.

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only state readOnlyHint=false and destructiveHint=false, so the description carries the behavioral burden. It expands significantly by explaining dedup semantics on `(agent, prospect, bucket_name)`, the `created=False` return path with existing row details, and when re-flagging is or is not allowed. This is rich, non-obvious behavior disclosed clearly.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every section earns its place: core action, canonical examples, exclusions/alternatives, sequencing distinction, dedup rules, and return shape. It is front-loaded with the most important information and uses clear structural markers (`<summary>`, `<returns>`).

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given there is no output schema, the description compensates by stating the exact return fields. It covers selection criteria, exclusions, dedup edge cases, and parameter semantics. An agent has enough context to decide when to call this tool and to interpret its response without additional documentation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% with already-detailed parameter descriptions, so the baseline is 3. The description adds value above the schema by explaining `bucket_name`'s role as a dedup key and UI label, and by noting that `action_description` is included when a duplicate is detected. It does not add much for `completion_criteria`, but the schema handles that parameter adequately.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Flag a prospect for the user to act on directly.' It clearly distinguishes itself from sibling tools by contrasting with `setup_linkedin_sequence`, email tools, `find_email`, and `define_sequence`, so an agent can tell exactly what this tool is for.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit when-to-use and when-not-to-use guidance: use for off-platform or human-judgment asks, 'only after ruling out' on-platform alternatives, and use `define_sequence` with a `manual` node for sequenced campaign steps. This is detailed routing guidance that leaves little to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

resolve_dateResolve DateA
Read-only
Inspect

Also use this to convert times in other timezones to the user's local timezone. For example, if someone says "2pm PST" and the user is in Eastern, call resolve_date("2pm PST") and it will return the equivalent time in the user's timezone. Use standard timezone abbreviations (PST, PDT, EST, EDT, CST, CDT, MST, MDT, GMT, UTC, etc.) -- "Pacific" or "Eastern" alone won't parse.

IMPORTANT: When using the result for trigger_at in add_trigger, ALWAYS use the utc_iso field from the response (e.g. "2026-04-04T04:56:00Z"). The time and time_12h fields are in the user's LOCAL timezone — passing them with a "Z" suffix will create triggers at the wrong time. Dict with date, weekday, and (when time is present) time, time_12h, and utc_iso (UTC ISO format for use in triggers).

ParametersJSON Schema
NameRequiredDescriptionDefault
expressionYesNatural language date/time expression (e.g. "Tuesday", "next Monday", "2pm PST", "3:30 PM EDT Thursday")

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description discloses important behavioral details: standard timezone abbreviations are required, partial names like 'Pacific' will not parse, and the returned time/time_12h fields are local while utc_iso is needed for triggers. This gives the agent essential execution context that annotations alone do not provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well structured with a front-loaded summary, concrete use cases, an important warning, and a returns section. Every part adds necessary information, and the critical trigger-time warning is clearly marked as IMPORTANT.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's low complexity, single parameter, and readOnly annotation, the description fully covers what an agent needs: usage context, parsing constraints, output field meanings, and the UTC danger case. The returns block compensates for the absence of an output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already documents the single expression parameter with examples at 100% coverage. The description adds further value by supplying additional relative-expression examples, clarifying timezone abbreviation requirements, and explaining how the expression maps to different output fields, especially utc_iso for triggers.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: resolving a natural language or relative date/time expression into an actual date and optionally time. It clearly distinguishes its role from sibling tools by noting it should be used before searches, calendar events, or any operation needing a concrete date. The timezone conversion purpose is also explicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit when-to-use guidance for relative expressions, timezone conversions, and date-requiring operations like create_calendar_event. It does not provide when-not-to-use guidance or name a directly competing alternative, so it falls just short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

retry_blocked_enactmentRetry Blocked EnactmentAInspect

A retry with the cause still in place fails and re-blocks identically — fix the cause in the same call: pass email for an email-shaped block (email_not_found, a missing address) and/or linkedin_url for a wrong or missing LinkedIn identifier (missing_identifier, a failed resolution). Both stamp the EXISTING prospect row — never re-add the person via track_prospects with the new contact info, which creates a duplicate prospect. A cr_already_sent block has no fix here: Sliq's records show it already sent this person a connection request. Tell the user; if they have since re-invited by hand, register_manual_linkedin_invites picks the campaign up from that invite; otherwise drop the prospect with update_prospect(stage='skipped'). A name_mismatch block means the LinkedIn profile belongs to someone other than the person they were added as (data.supplied_name): retrying continues the campaign with that profile, with no new lookup — do it only when it's the same person under another name; otherwise skip the prospect and track the intended person (a linkedin_url fix is refused on this block). Dict confirming the prospect, the node they rest on, the cleared reason, and any contact fixes applied (email / linkedin_url). An email fix lands on the campaign row and the person's profile; profile_note is present only when the profile was left as it was because another person in the workspace already carries that address — pass the note on to the user.

ParametersJSON Schema
NameRequiredDescriptionDefault
emailNoAn address to stamp on the prospect before retrying (optional) — the same write as update_prospect's `email`; refused if another prospect in the account already owns it.
agent_idYesID of the agent whose flow the prospect is on.
prospect_idYesThe prospect's `id` — the outreach_prospects primary key, as returned on every query_prospects row.
linkedin_urlNoA corrected LinkedIn profile URL or slug (optional). Applied as an unverified claim the retried send verifies through the profile resolver, so a wrong-person pairing is held for adjudication like any other. On a previously-verified profile (a dead or reassigned URL) the old verification is cleared and the corrected identity re-verifies fresh; refused only when a teammate's prospect verified the same person — remove this prospect and track the correct profile instead.

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only indicate readOnlyHint=false and destructiveHint=false, providing minimal safety context. The description goes far beyond by disclosing side effects: it clears the block, leaves the prospect unstamped, and re-fires on a later tick (no immediate dispatch). It also warns that retrying with the cause still in place fails and re-blocks identically, and that it never re-adds the person to avoid duplicates. It explains the behavior for each block type, including the profile_note edge case. This is rich behavioral transparency that the annotations do not cover.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but well-structured with <summary> and <returns> sections. The main purpose is front-loaded in the first sentence, and each subsequent sentence adds necessary detail about block types, fixes, and alternatives. There is no fluff; every sentence earns its place given the complexity of the tool. The structure aids comprehension.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (multiple block types, conditional fixes, and alternatives), the description is complete. It covers the action, the conditions for each block type, what to do when a fix isn't possible, the return value, and edge cases like profile_note. Nothing an agent needs to invoke this tool correctly is missing; the output schema is not present, but the <returns> section describes the return structure sufficiently.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents each parameter. However, the description adds significant meaning beyond the schema: it explains that 'email' is for email-shaped blocks (email_not_found, missing address) and 'linkedin_url' for wrong/missing LinkedIn identifiers, and clarifies that both stamp the EXISTING prospect row. It also explains the 'linkedin_url' behavior regarding unverified claims and verification clearing, which is not in the schema. This greatly enhances the agent's ability to use the parameters correctly.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a clear verb-resource pair: 'Clear a prospect's blocked flow step so it retries.' It specifies the exact resource (blocked flow step) and the action (clear and retry), and immediately distinguishes itself from related tools like report_enactment_blocked and query_prospects by referencing them and explaining the difference. This is unambiguous and highly specific.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit when-to-use guidance: it is for blocked prospects, and it details the conditions for each block type (email_not_found, missing_identifier, cr_already_sent, name_mismatch). It also states when NOT to use it (for cr_already_sent there is no fix here, and for name_mismatch only when it's the same person), and names alternatives (register_manual_linkedin_invites, update_prospect, track_prospects). This is comprehensive and leaves no ambiguity about selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

save_memorySave MemoryAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
contentYesThe full updated memory document (markdown)

TDQS

A4.3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations declare readOnlyHint=false and destructiveHint=false, which are somewhat ambiguous for a save operation, but the description clarifies that the entire document is replaced, which is important destructive behavior. The description adds the crucial 'replaces the entire document' detail that annotations do not convey. However, it doesn't mention potential side effects or authorization requirements, leaving a moderate gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is exceptionally concise and well-structured with a summary and returns section. The critical instruction to pass the complete document is front-loaded, and there is no filler. Every sentence provides value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter tool with full schema coverage and a simple return value (success dict), the description is complete for an agent to call it correctly. It explains the workflow (read, modify, pass full content) and the parameter semantics are fully covered by schema and description. The return is described as a success dict, which is sufficient.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the parameter 'content' is already described as 'The full updated memory document (markdown)'. The description reinforces that the content must be the complete document, but adds no new formatting or usage details beyond the schema. The baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool saves the user's memory document and emphasizes that the complete document must be passed, distinguishing it from read-type tools like get_user_memory. The verb 'save' and resource 'memory document' are specific and unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly instructs the agent to read the current memory, make changes, and pass the full result, providing a clear workflow. It also implicitly distinguishes from get_user_memory by indicating this is for updating. While it doesn't name alternatives, the guidance is clear enough for correct usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

save_prospect_researchSave Prospect ResearchAInspect

Call this after you synthesize a "why I'm reaching out" angle for a recipient — once per recipient, batching all of that person's findings (and you may batch across recipients). Each finding is YOUR synthesized insight ("former Stripe PM, just posted about scaling support"), NOT raw scraped post text or an article abstract.

Pass the lead as a handle (the LinkedIn URL / provider id / email you already hold) in person — do NOT hand-type a canonical identifier; the tool canonicalizes it for you. A company finding (subject_kind='company') still attaches to the person it personalizes; set company_domain to record the company. Dict with created (the count of saved research rows).

ParametersJSON Schema
NameRequiredDescriptionDefault
agent_idYesThe agent these findings belong to.
findingsYesOne ProspectFinding per synthesized insight; each carries the lead handle it personalizes, its subject_kind, and the source.

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only state readOnly=false and destructive=false, so the description adds the meaningful behavioral details: it persists rows, returns a created count, canonicalizes the person handle, and attaches company findings to the person. It does not disclose duplicate/overwrite behavior or auth requirements, but it adds substantial context beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The definition is front-loaded with the core purpose, then gives tight usage rules in three short paragraphs. Every sentence carries operational guidance; the repeated schema details (person handle, company_domain) reinforce rather than pad.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with minimal annotations and no output schema, the description covers trigger, batching, content type, person reference, company attachment, and return value. It is just short of a 5 because it does not specify duplicate or upsert behavior if called more than once for the same recipient.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real semantics: pass the lead as a handle already held rather than hand-typing a canonical id, and company findings still attach to a person with company_domain recording the company. It also defines the required content shape as synthesized insight, not raw scraped text.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Persist synthesized personalization research for leads on an agent,' and clarifies the end result ('what Sliq found'). It separates itself from sibling save/search/record tools by stressing that each finding is synthesized insight, not raw scraped text or article abstracts.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives an explicit trigger ('Call this after you synthesize a why-I'm-reaching-out angle'), per-recipient batching guidance, and an explicit content exclusion (not raw scraped content). It does not name the read-side sibling (query_prospect_research) or say when not to call, but the invocation context is otherwise clear among the large sibling set.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_calendarSearch CalendarA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum number of results (default 50, max 200). Only override if user says specific number like "show me 10 events". For vague quantities ("couple", "few", "recent"), omit this parameter to use default.
offsetNoRows to skip for paging (default 0). When the result is truncated, re-call with offset += limit to fetch the next page.
order_byNoSQL ORDER BY clause (default: event_start DESC)event_start DESC
where_clauseYesSQL WHERE clause (without WHERE keyword). Use ONLY these columns: provider, subject, event_start, event_end, location, meeting_link, organizer, attendees, is_all_day, is_cancelled, has_recurrence. For organizer/attendees JSONB, use: organizer::text ILIKE '%pattern%' or attendees::text ILIKE '%pattern%'

TDQS

A3.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The readOnlyHint annotation already covers safety. The description adds valuable behavioral context beyond that by describing the return structure (count, truncated flag indicating more rows) and pagination behavior, which helps the agent understand how results are paged and truncated.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and well-structured: a summary sentence plus a returns section. Every sentence is purposeful, and the key instruction (use exact column names) is front-loaded. No wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only search tool with rich schema documentation and a readOnlyHint annotation, the description covers the essential context: the data source, the return shape, and pagination cues. It does not list event fields, but the schema's column list in where_clause suffices. A minor gap is the lack of any note about whether ordering or filtering options are mandatory, but overall it is complete enough.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so each parameter is already documented. The description's instruction to use exact column names in where_clause reinforces the schema but adds no new meaning. Baseline of 3 is appropriate given the schema carries the semantic load.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool searches user's calendar events from table_calendar_data, identifying the specific verb ('search') and resource. However, it does not explicitly distinguish itself from similar sibling tools like search_meetings, so it stops short of a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool over alternatives such as search_meetings or list_prospect_events. It only states what it does, leaving the agent to infer the appropriate context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_chat_historySearch Chat HistoryA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum number of sessions to return (default 20, max 50).
keywordNoSearch term to match against session title and summary (case-insensitive). Leave empty to browse recent sessions.
created_afterNoOnly return sessions created after this date (ISO format, e.g. '2026-01-15').
created_beforeNoOnly return sessions created before this date (ISO format, e.g. '2026-03-01').

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The readOnlyHint annotation already signals a safe read operation, so the description does not need to restate that. It adds value by describing the return shape (sessions with titles, summaries, dates), but does not mention ordering, pagination beyond the limit parameter, or any other behavioral details beyond what the schema and annotation already provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and front-loaded with the tool's core purpose, followed by a clear 'use this when' statement and a brief returns section. It contains no filler, and the structure makes the main purpose immediately visible.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given that all parameters are optional and fully documented in the schema, the annotation declares read-only behavior, and the description includes a return-shape summary, the tool is adequately specified for an agent to select and invoke it correctly. The only minor gap is the lack of sibling differentiation, but this does not block correct use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already documents all four parameters thoroughly with descriptions, defaults, and formats, yielding 100% schema coverage. The description adds no extra parameter semantics beyond the high-level summary, so the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states that the tool searches past chat sessions and returns session metadata, and pairs it with an explicit trigger ('when the user asks about previous conversations'). It is specific enough to be distinguished from search_emails or search_calendar, though it does not explicitly name any sibling alternative.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It provides a concrete usage context: 'when the user asks about previous conversations, past discussions, or wants to find something they talked about before.' This gives clear when-to-use guidance, but it does not mention when not to use it or point to alternatives like search_linkedin_message_history or get_chat_messages.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_emailsSearch EmailsA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum number of results (default 50, max 200). Only override if user says specific number like "show me 10 emails". For vague quantities ("couple", "few", "recent"), omit this parameter to use default.
offsetNoRows to skip for paging (default 0). When the result is truncated, re-call with offset += limit to fetch the next page.
order_byNoSQL ORDER BY clause (default: email_datetime DESC)email_datetime DESC
as_teammateNoRead a consented teammate's emails instead of your own — pass their email. Gated on that teammate's conversation-sharing setting; a teammate who hasn't shared is rejected. Omit for your own.
where_clauseYesSQL WHERE clause (without WHERE keyword). Use ONLY these columns: provider, sender_email, sender_email_lower (WHERE only; not returned; pass lowercased values), sender_name, recipients, is_outbound (BOOLEAN — true = sent from the connected mailbox, false = received), subject, content, email_datetime, attachments, reply_outcome (inbound replies only; '' / 'interested' / 'meeting_booked' / 'not_interested' — the reply's classified outcome). For participants, use sender_email_lower = ANY(ARRAY['foo@example.com', ...]). Resolve the email address first if you only have a name. For attachments JSONB, use: attachments::text ILIKE '%filename%' or attachments != '[]'::jsonb to find emails with attachments.

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=true, so the description carries the burden of behavior. It adds meaningful context: results are truncated to 200 chars, content_length contains the full length, and the return dict includes count and truncated. This goes beyond a simple read-only flag.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary is tight and front-loaded: it states the action, the need for exact column names, the truncation behavior, and the route to get_emails. The returns block is a single sentence. No wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the rich parameter schema (especially where_clause) and read-only annotation, the description supplies the missing pieces: return shape, truncation behavior, and the path to full content. Nothing needed for correct invocation is left out.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3. The description reinforces using exact column names from where_clause but adds no new parameter meaning beyond what the schema already describes in detail.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States the specific action and resource: 'Search user's emails from table_email_data'. It also clarifies the preview purpose with truncation and explicitly distinguishes from get_emails for full content, so an agent can tell them apart.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit guidance on when to use an alternative: 'To read full email content, call get_emails with the IDs you need.' Also gives parameter-level guidance, such as when to override limit versus omitting it for vague quantities.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_linkedin_connectionsSearch Linkedin ConnectionsA
Read-only
Inspect

position and company are connection-time snapshots and may be stale — treat them as a starting point, not current ground truth. Dict with count, truncated (True when more rows exist past this page), and connections array.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum number of results (default 50, max 200). Only override if the user says a specific number. For vague quantities ("a few", "recent"), omit this parameter to use the default.
offsetNoRows to skip for paging (default 0). When the result is truncated, re-call with offset += limit to fetch the next page.
order_byNoSQL ORDER BY clause (default: added_date DESC).added_date DESC
where_clauseYesSQL WHERE clause (without WHERE keyword). Use ONLY these columns: first_name, last_name, position, company, url, email_address, connected_on, linkedin_provider_id, added_date.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=true. The description adds meaningful behavioral context beyond that: position and company are connection-time snapshots that may be stale, and it describes the return shape including a truncated flag. This informs the agent about data freshness and pagination behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with summary and returns sections, front-loaded with purpose and exclusions. It is slightly verbose but every sentence adds value, and the length is justified by the need to clarify scope and caveats.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a search tool with four parameters, this description is complete: it states the data source, the typical use cases, what it is NOT, the exact column constraints, a staleness caveat, and the return format. Nothing an agent needs to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already documents all four parameters with descriptions and defaults (100% coverage). The description adds only a reminder to use exact column names from the where_clause and notes that position/company are stale snapshots. These are helpful but not substantive additions beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a clear verb and resource: 'Search the user's established first-degree LinkedIn connections'. It is explicit about what the tool does and distinguishes it from message history and outreach prospects by naming the alternatives. Example queries further clarify the intended use.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit when-to-use guidance ('Use to find existing relationships by name, title, or company') and explicit when-not-to-use with named alternatives ('This is NOT message history (use search_linkedin_message_history) and NOT outreach prospects (use query_prospects)'). This leaves no ambiguity for an agent.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_linkedin_message_historySearch Linkedin Message HistoryA
Read-only
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum number of results (default 50, max 200). Only override if the user says a specific number like "show me 10 messages". For vague quantities ("couple", "few", "recent"), omit this parameter to use the default.
offsetNoRows to skip for paging (default 0). When the result is truncated, re-call with offset += limit to fetch the next page.
order_byNoSQL ORDER BY clause (default: message_datetime DESC).message_datetime DESC
as_teammateNoRead a consented teammate's LinkedIn message history instead of your own — pass their email. Gated on that teammate's conversation-sharing setting; a teammate who hasn't shared is rejected. Omit for your own.
where_clauseYesSQL WHERE clause (without WHERE keyword). Use ONLY these columns: provider, chat_id, sender_name, sender_provider_id, recipient_name, recipient_provider_id, content, message_datetime, reply_outcome (inbound replies only; '' / 'interested' / 'meeting_booked' / 'not_interested' — the reply's classified outcome). To check whether a prior conversation exists before firing a templated message, match on `sender_provider_id`/`recipient_provider_id` first; if that returns zero rows, run a second pass matching the person's full name (first AND last) as a substring, e.g. `sender_name ILIKE '%Jane%Doe%' OR recipient_name ILIKE '%Jane%Doe%'`, before concluding there's no history — a stored provider_id is often blank or wrong, so an exact-match zero is indistinguishable from "never messaged." Never match on first name alone (it pulls every same-first-name person in the inbox and risks dropping a cold message onto a stranger's live thread). The full-name pass is a substring match, so if it spans more than one person (different provider_ids), use only the thread whose counterpart is your recipient — don't merge look-alike names or reuse a mismatched `chat_id`. Even both passes empty isn't proof of no prior contact: this local store holds only a bounded initial backfill from around when the account was connected, forward, so an older or pre-connection thread may never have been ingested — and the same person may be stored under a variant name a substring match misses. When you have the counterpart's `provider_id` and need certainty, call `fetch_linkedin_messages_with_person` — it reads LinkedIn's full synced history live and writes it back here; if that also returns empty, treat "no history" as confirmed, otherwise flag the uncertainty when a cold-open template goes to approval.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Read-only status is already covered by readOnlyHint, but the description adds substantial non-obvious behavioral context: content is truncated to 1000 chars, the local store holds only a bounded initial backfill so an empty result is not proof of no contact, and as_teammate is gated on the teammate's sharing setting. This exceeds what annotations provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary/returns structure is front-loaded and each clause is informative rather than filler. Slightly dense, but the trimming would lose the disambiguation and truncation facts, so the length is largely earned.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers the return shape inline (count, truncated, messages, chat_id hand-off), the data-completeness caveat, the teammate-sharing gate, and the alternative tool. For a read-only search tool with no output schema, nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the description's parameter-relevant content (column names, chat_id reuse) is largely duplicated from or delegated to the schema. With the structured fields doing the heavy lifting, baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Search the user's LinkedIn message history (synced conversations)') and immediately disambiguates from the sibling 'fetch_linkedin_messages_with_person' by explaining which artifact each returns. An agent can distinguish this local-store search from the live-history fetch without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use guidance ('call this once per person (loop in run_code)') and a concrete escalation path: when the counterpart's provider_id is known and certainty is required, use fetch_linkedin_messages_with_person instead. It also documents the join to setup_linkedin_sequence for follow-ups, so the routing decision is fully specified.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_linkedin_peopleSearch Linkedin PeopleA
Read-only
Inspect

One call runs one query, returning ~10 matches by default (one page). To go deeper on a single query — "find people in my network matching my ICP" — pass max_results (up to 100): the tool pages through the matches for you, each ~10-profile page counting as one search against the daily budget. To search different people — a list of names, or one filter per company — loop this tool inside a run_code block, one call per name or company (that's breadth; max_results is depth on one query). Searches are paced a few seconds apart and serialized across this user's LinkedIn work, so a deep search or a long loop can take a couple of minutes; tell the user to expect a short wait before a large run. If a search comes back paused or rate-limited, stop and tell the user which searches remain — the account is paused and further calls won't run until it lifts.

Scope filters combine with keywords and can be used alone for a single filtered search:

  • connections_of — restrict to the first-degree connections of specific people, passed as their provider_ids (as returned by an earlier search or profile lookup). To work up to a buyer through someone the user just connected with, pass that person in connections_of and the target company in advanced_keywords={'company': 'Acme Corp'} to surface who they know there. network_distance is a separate filter on the user's own degree and combines with this — add [2] to keep just the connections the user isn't already directly linked to.

  • advanced_keywords — native LinkedIn keyword sub-filters: a dict with any of first_name, last_name, title, company, school (each a string).

  • profile_language — ISO 639-1 codes (e.g. ['en']) that narrow any of the above to profiles written in those languages. A refinement, not a search on its own — pair it with keywords or another filter.

When this runs in an agent, the matches are saved and linked to the workspace Output tab automatically (deduped by profile). Pass list_name (a short slug) to name their list — a discovery search, the people connected to someone, prospects to work through; reuse the same slug across a loop or follow-up searches to gather everything into one list. Absent a slug, matches land in the 'default' list. Outside an agent, results are returned only.

Returns up to max_results matching profiles with provider_id, name, headline, network_distance, location, and profile_url. Each match's headline is the member's own tagline: often their current role and company (e.g. to see which companies 2nd-degree matches work at), but frequently a title alone, so a headline that omits a company is not evidence they work elsewhere. total_count is LinkedIn's full match count for the query when it returns one, but LinkedIn now omits it on most Classic searches (so it's often null): only say "showing N of ~M" when it's a number exceeding the profiles returned, and never invent a total. Use has_more — True when more results exist beyond those returned — to decide whether to offer to pull more. Present the results to the user so they can pick the right person. An error about being "heavily queued" is transient pacing back-pressure — retry shortly rather than reporting it as not found.

That field list is the whole of it — a search result carries no connection count, follower count, or employment history. Present what comes back as it is; a search the user wanted to look at is finished at that point. When the ask genuinely needs one of the missing fields — a connection-count threshold, employment history to personalize from — pass the matches' profile URLs to enrich_linkedin_profiles, which returns them for the whole list in one paid call (connections_count is the field a connection-count filter reads) and spends no LinkedIn account budget. When the decision also turns on whether the user is already connected to them, use setup_linkedin_sequence(action_type='resolve') instead; connection status is the one thing enrichment cannot answer.

Rate-limited — shares one daily LinkedIn search budget with all other LinkedIn people searches. On success, a dict {'success': True, 'profiles': [...], 'total_count': int | None, 'has_more': bool, 'searches_remaining_today': int}. profiles holds up to max_results matches; total_count is the query's full match count when LinkedIn returns one (often null since its Aug-2026 Classic Search change), so lean on has_more for whether more results exist; and searches_remaining_today is the post-search budget, so you can size a follow-up loop without re-checking. In an agent, also saved_to_list (the list the matches were saved to) and saved_count; outside an agent, a passed list_name yields a null saved_to_list with a persist_note. If the account tripped its pause partway through paging, the (still valid) partial results come back with paused: True and a note — surface it: further searches won't run until the pause lifts.

On a failed search: {'success': False, 'profiles': [], 'error': ..., 'searches_remaining_today': int}. On a pre-flight refusal (daily limit reached or account paused), searches_remaining_today is omitted: {'success': False, 'error': ...}.

ParametersJSON Schema
NameRequiredDescriptionDefault
keywordsNoA single search query, typically a person's name (e.g. "John Smith"). Omit when searching by `connections_of` / `advanced_keywords` alone. To search many names, loop the tool in `run_code`, one name per call.
list_nameNoOptional short slug naming the Output-tab list the matches are saved under when this runs in an agent. Absent, they save to the 'default' list. Reuse the same slug across a loop or follow-up searches to gather them into one list (deduped by profile).
max_resultsNoMax profiles to return for this one query (default 10 = one page; capped at 100). The tool pages the search internally to reach this many, each ~10-profile page costing one search from the daily budget. Use it to go deep on a single ICP/network query; for many *different* queries, loop the tool instead.
connections_ofNoOptional list of provider_ids; restricts results to people connected to those individuals.
network_distanceNoOptional LinkedIn degree filter. Pass [1] for first-degree connections, [2] for second-degree, [3] for third-degree-or-more, or combinations like [1, 2].
profile_languageNoOptional list of ISO 639-1 codes (e.g. ['en']).
advanced_keywordsNoOptional dict of native keyword sub-filters (first_name / last_name / title / company / school).

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint, but the description adds substantial behavioral context: a shared daily search budget, per-page cost, ~10-match default, a few-seconds pacing that is serialized across the user's LinkedIn work, pause/rate-limit handling, transient "heavily queued" errors, and auto-save to the Output tab. This is far beyond what the annotation conveys.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary is front-loaded and well-organized into logical paragraphs, but the text is very long and some field/return details duplicate the `<returns>` block (e.g. total_count, has_more explanations). Most sentences earn their place, yet trimming redundancy would improve scanability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool this complex—optional params, budget limits, paging, saving behavior, and enrichment fallbacks—the description plus the returns block cover the workflow end to end. An agent has enough to invoke it correctly and interpret results without guessing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3, but the description adds real meaning beyond the schema: a worked example combining `connections_of` with `advanced_keywords={'company': 'Acme Corp'}`, the note that `network_distance` is on the user's own degree and combines with connections_of, and that `profile_language` is a refinement not a standalone search.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource and immediately enumerates the three search modes (by name, by a person's connections, by profile filters) plus the return shape. It clearly differentiates itself from siblings like enrich_linkedin_profiles, search_linkedin_connections, and exa_find_people.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly contrast depth (`max_results` on one query) vs breadth (looping the tool in `run_code`, one name/company per call), and names the sibling to use for missing fields (enrich_linkedin_profiles) vs connection status (setup_linkedin_sequence). It even tells the agent when to stop and how to handle pause/rate-limit conditions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_meetingsSearch MeetingsA
Read-only
Inspect

When the user has Grain, Granola, or Otter connected, prefer this tool first (Fathom data is local and faster) and fall back to grain_*/granola_*/otter_* only if no relevant results or the user explicitly references that source. Dict with count, truncated, and meetings array (summary truncated to 6000 chars, transcript truncated to 10000 chars — use get_meeting_transcript(id) to fetch the full transcript for a specific meeting)

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum number of results (default 10, max 20). Only override if user says a specific number like "show me 5 meetings". For vague quantities ("couple", "few", "recent"), omit this parameter to use default.
offsetNoRows to skip for paging (default 0). When the result is truncated, re-call with offset += limit to fetch the next page.
order_byNoSQL ORDER BY clause (default: meeting_date DESC)meeting_date DESC
where_clauseYesSQL WHERE clause (without WHERE keyword). Use ONLY these columns: provider, title, meeting_date, duration, speakers, attendees, meeting_url, share_url, summary, transcript. For speakers/attendees JSONB, use: speakers::text ILIKE '%pattern%' or attendees::text ILIKE '%pattern%'

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, and the description adds meaningful behavioral details: results are returned as a dict with count/truncated/meetings, summaries are truncated to 6000 chars, transcripts to 10000 chars, and fetching full transcripts requires a separate call. This goes well beyond the annotation and schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and well-organized, front-loading the core purpose and routing guidance, then noting return behavior. The instruction to use exact column names is slightly redundant with the schema, and the Grain/Granola/Otter context could arguably live in usage guidance, but overall every section earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only search tool with no output schema, the description provides the key context an agent needs: source table, return shape, truncation limits, how to get full transcripts, and when to switch to alternative tools. The limit/offset behavior and column constraints are already fully documented in the schema, making this complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all parameters, including the where_clause column list and JSONB ILIKE syntax. The description adds little parameter-level meaning beyond referencing 'exact column names listed under where_clause', so the baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action ('Search user's meeting data from table_meeting_data') with a clear resource and scope. It also differentiates from related meeting-source tools by explaining when to prefer this tool over grain_*/granola_*/otter_*, so an agent can select it correctly among siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage guidance is explicit: prefer this tool first when Grain/Granola/Otter are connected, and fall back to sibling source tools only when no relevant results appear or the user names that source. It also directs the agent to get_meeting_transcript(id) for full transcripts, covering the key alternative.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_monitor_eventsSearch Monitor EventsA
Read-only
Inspect

Search the user's monitor event log. Use for scheduled digests (only_unnotified=True) and ad-hoc questions ('what's the latest on Stripe?').

Returns a dict with total (full match count), truncated (True when more rows exist past this page — page with offset to reach them), and events.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax results (default 50, max 200)
sinceNoISO datetime or natural language like 'last 7 days' (optional)
offsetNoRows to skip for paging (default 0). When `truncated` is True, re-call with offset += limit to fetch the next page.
agent_idNoFilter by specific monitor agent. Omit to search across all your agents (inside an agent run, an omitted agent_id defaults to that run's agent).
company_domainNoFilter by company display name or domain — matches either (e.g. 'stripe.com' or 'Stripe'). Optional.
min_confidenceNoMinimum confidence score 1-10 (default 1)
only_unnotifiedNoOnly return events not yet included in a digest (default False)

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only provide readOnlyHint=true, which describes the safety profile but not behavior. The description adds important behavioral details: it returns a dict with specific keys (`total`, `truncated`, `events`), explains pagination via `truncated` and `offset`, and clarifies count semantics. This goes beyond the schema and gives the agent critical operational understanding.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three sentences: the first states the purpose, the second gives use cases, the third explains the return structure. It is front-loaded with the core action, and every sentence earns its place without fluff or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With 7 parameters, 100% schema coverage, and no output schema, the description covers the essential output shape and pagination, which is typically the missing piece. There are no extras like enums or nested objects to clarify. The description is complete for an agent to use the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already has 100% coverage with descriptive parameter descriptions, including defaults and usage notes (e.g., offset paging, agent_id fallback). The description adds a bit by mentioning 'only_unnotified=True' for digests, but the schema already explains each parameter. Baseline 3 is appropriate given the high schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'Search the user's monitor event log' with a specific verb and resource. It distinguishes itself from sibling search tools by focusing on monitor events and hints at its use for digests. The first sentence is precise and prevents confusion with other search_* tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly mentions two use cases: scheduled digests and ad-hoc questions, giving the agent clear context when to use this tool. It also implies the alternative (using only_unnotified for digests) by highlighting the parameter. Although it doesn't name alternative tools, the sibling list makes it clear that other search tools exist, but the description's guidance is sufficient.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_news_eventsSearch News EventsA
Read-only
Inspect

⚠️ DISCOVERY ONLY — NOT a company lookup. This returns a global feed of the most recent events matching categories + company_locations (deduped by company domain, ~10 rows). It takes NO company / domain / name input and CANNOT be scoped to a specific company or to a list of known companies. Use it ONLY for find-companies discovery ("surface net-new companies that just did X"). To research signals for a SPECIFIC named company or an uploaded prospect/account list, this is the WRONG tool — use tavily_search with a company-scoped query (e.g. " customer success news 2026") instead. search_monitor_events(company_domain=...) works too, but only for companies the user already monitors.

Use this when the user asks to DISCOVER companies by a signal that maps to one of the supported categories below. Only fall back to tavily_search if this returns no results or the signal isn't covered.

Supported categories (pass one or more in a single call): Expansion: expands_facilities, expands_offices_in, expands_offices_to, opens_new_location, increases_headcount_by, attends_event Investment: receives_financing, goes_public, invests_into, invests_into_assets, has_earnings, has_revenue, has_valuation Leadership: hires, leaves, promotes, retires_from Contract: signs_new_client, loses_client Cost cutting: decreases_headcount_by, closes_offices_in New offering: launches, is_developing, integrates_with Partnership: partners_with, ends_partnership_with Acquisition: acquires, merges_with, sells_assets_to Challenges: declares_bankruptcy, files_suit_against, has_issues_with Recognition: receives_award, recognized_as Relational: identified_as_competitor_of, spins_off_company, spins_off_division Dict with events array and count. If every location-query failed with an upstream error (timeout / 5xx) and nothing landed, also predictleads_available: False plus a human-readable error — a source outage, NOT a genuinely empty feed. Partial success (some locations errored, others returned events) stays silent and reports only the events.

ParametersJSON Schema
NameRequiredDescriptionDefault
categoriesYesOne or more category names from the enumerated list above (the parameter is constrained to exactly those values). Map common intents to the right name: "funding" → receives_financing, "IPO" → goes_public, "acquisition" → acquires, "layoffs" → decreases_headcount_by. Pass all relevant categories in a single call — they are fetched together at no extra cost.
lookback_daysNoDays of news history to scan (default 30). A news scan passes the window from its trigger prompt.
company_locationsNoFilter by country or US state. Pass a list to cover multiple locations (e.g. ["United States", "Canada"]). At most 10 API requests are made across all locations. Omit to search globally. Prefer country-level values (e.g. "United States") over individual cities/states unless the ICP is tightly city-scoped.

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only carry readOnlyHint=true, so the description carries the full disclosure burden and does so well: dedup by company domain (~10 rows), no company input allowed, and the failure semantics in the returns section (predictleads_available flag on full outage vs silent partial success). The only minor gap is not mentioning rate limits or auth requirements, but those are not expected for a read-only tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Long but every section earns its place: the constraint warning is front-loaded, the category list is organized into readable groups, and the returns section is separate. The only fat is the repeated emphasis on the discovery-only constraint, which appears in both the summary and the usage paragraph — defensible for a critical disambiguation but slightly redundant.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, so the returns section explains the event array, count, and the error/availability fields. All 3 parameters are fully covered, the category enum is exhaustively enumerated with usage guidance, and the tool's complexity (multi-category, multi-location, failure modes) is matched by the description's depth. Nothing an agent needs to call this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Despite 100% schema coverage, the description adds substantial value beyond the schema: intent-to-category mapping examples ('funding' → receives_financing, 'layoffs' → decreases_headcount_by), advice to pass all categories in one call, location-filtering guidance (prefer country-level over cities), and the 10-request cap. This materially improves an agent's ability to map user intent to correct parameter values.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('Discover'), resource ('recent news events across ALL companies'), and scope ('filtered by category'). The bold warning 'DISCOVERY ONLY — NOT a company lookup' immediately disambiguates it from company-scoped siblings, and the full category list pins down the exact signal types. No ambiguity about what this tool does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names the two alternatives (tavily_search with a company-scoped query for specific companies, search_monitor_events for monitored companies) and states the exact condition that selects this tool ('find-companies discovery'). Also specifies the fallback rule ('Only fall back to tavily_search if this returns no results'). The when-not-to-use guidance is unambiguous.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_social_postsSearch Social PostsA
Read-only
Inspect

Search social listening posts created by the webhook listening pipeline. Use for notifications and ad-hoc questions about social listening results.

Returns a dict with total (full match count), truncated (True when more rows exist past this page — page with offset to reach them), and posts.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax results (default 50, max 200)
sinceNoISO datetime or natural language like 'last 24 hours' (optional)
offsetNoRows to skip for paging (default 0). When `truncated` is True, re-call with offset += limit to fetch the next page.
channelNoFilter by channel, for example 'reddit' or 'hacker_news' (optional)
agent_idNoFilter by specific social listening agent. Omit to search across all your agents (inside an agent run, an omitted agent_id defaults to that run's agent).
only_unnotifiedNoOnly return posts not yet included in a digest (default False)

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations include readOnlyHint=true, so the safe-read nature is already known. The description adds valuable behavior beyond that: it explains the return structure (dict with `total`, `truncated`, `posts`) and how pagination works (`truncated` indicates more rows and suggests using offset). This goes beyond the schema and helps the agent handle results correctly.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and front-loaded: it states the purpose in the first sentence, usage context in the second, and return contract in the third. No word is wasted, and the structure makes it easy to scan.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 6 optional parameters and no output schema, the description covers the essential return shape and pagination behavior. It does not explicitly mention default time window or sort order, but those are not critical for basic invocation finduse. It is complete enough for an agent to call the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, with all six parameters documented in the input schema (defaults, types, examples). The tool description itself does not add extra parameter-level nuance, so it stays at the baseline 3 for fully covered schema parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb and resource: 'Search social listening posts created by the webhook listening pipeline.' It is specific about the source (webhook pipeline) and distinguishes it from generic post searches, though it does not explicitly name sibling tools to differentiate from them.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit usage context: 'Use for notifications and ad-hoc questions about social listening results.' This tells the agent when to invoke it, but it does not mention exclusions or alternative tools, leaving some room for confusion among many search siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

send_emailSend EmailA
Destructive
Inspect

The sending mailbox is resolved automatically from the draft log created by draft_email or draft_reply — no mailbox arg needed. Dict with success status. On a Superhuman mailbox it also carries sent_at, up to a minute ahead of the call — Superhuman holds the message for its Undo Send window and it leaves the outbox then. If the user says a just-sent message hasn't arrived, tell them it lands at sent_at rather than sending it again.

ParametersJSON Schema
NameRequiredDescriptionDefault
draft_idYesThe draft_id returned by draft_email or draft_reply
providerNoDeprecated and ignored — the mailbox is resolved from the draft log. Omit it.
as_teammateNoSend a draft you created on a consented teammate's behalf — pass the SAME email you passed to draft_reply. The send goes out from their mailbox. Gated on that teammate's act-on-behalf setting; a teammate who hasn't granted it is rejected. Omit to send your own draft.

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already indicate destructiveHint=true, so the description doesn't need to restate that. The description adds valuable behavioral context: the Superhuman Undo Send window means sent_at can be up to a minute ahead, and the message leaves the outbox then. It also explains the as_teammate gating on act-on-behalf setting. This goes beyond annotations and helps the agent understand real-world behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with a summary section and a returns section. It front-loads the critical confirmation requirement, then explains the mailbox resolution, then covers the return behavior. Every sentence earns its place, and the formatting with <summary> and <returns> tags makes it scannable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a send action with 3 params, 100% schema coverage, and no output schema, the description is complete. It covers the confirmation prerequisite, the return value, the Superhuman timing quirk, the teammate gating, and the deprecated provider param. An agent has everything needed to call this correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all three parameters. The description adds value by explaining the provider param is deprecated and ignored, and by clarifying that as_teammate must be the SAME email passed to draft_reply. This is meaningful semantic guidance beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Send a previously created email draft.' It clearly distinguishes this from draft_email and draft_reply by noting it is the send step after drafting, and it explicitly says the mailbox is resolved automatically from the draft log. This makes the tool's purpose unambiguous and distinct from siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says 'Only call this AFTER the user has explicitly confirmed they want to send the draft,' which is a clear when-to-use condition. It also explains that no mailbox arg is needed because the mailbox is resolved from the draft log, and it warns against resending when a user says a just-sent message hasn't arrived. This is strong usage guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

send_luma_invitesSend Luma InvitesA
Destructive
Inspect

Luma emails each recipient an invite to the event and adds them to the roster as invited. This is a real send with no approval step and no undo — treat calling it as sending, and confirm the recipient list with the user first unless they have already approved this exact wave.

Distinct from the LinkedIn/email outreach queues: those are Sliq-sent cold sequences with pacing and approval gating, whereas this is Luma's branded event invite from the calendar the user owns. Use the queues to pitch the event to people who don't know it exists; use this to invite people who should land directly on the event's guest list.

Anyone already on the roster — invited, going, pending approval, waitlisted, or declined — is skipped, so re-running with the same list invites only the genuinely new people. That makes retries safe.

Every invite is recorded against the matching person Sliq tracks (under data['luma_invites'], one entry per event), so who-we-invited is answerable from our own records and not only from Luma's roster.

Recipients Sliq isn't tracking are still invited — they just get no such record, which means an RSVP from them won't appear in the funnel. That is normal (hand-typed addresses, people who were never prospects), and most waves have some, but the user can't see it from Luma's side. When not_tracked is non-empty, say so in the reply — how many, and which addresses — instead of reporting a clean send. A summary dict {event_id, invited: [...], invited_count, skipped: [{email, reason}], skipped_count, tracked_count, not_tracked: [...], not_tracked_count} — plus note, user-facing wording for the untracked recipients, present only when there are any. invited lists the addresses actually sent to; a fully-deduplicated call returns invited_count 0 and is a success, not an error. tracked_count counts recipients Sliq already knows; it is lower than invited_count whenever the wave includes people who aren't tracked.

ParametersJSON Schema
NameRequiredDescriptionDefault
emailsYesRecipient email addresses. Deduplicated case-insensitively, then filtered against the live roster.
messageNoOptional personal note included in the invite email, max 200 characters. Written for the whole wave — Luma has no per-recipient placeholder substitution here, so don't try to personalize it with names.
event_idYesThe Luma event id from get_luma_events (starts with "evt-").

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare destructiveHint=true and readOnlyHint=false, and the description fully corroborates and extends this: 'real send with no approval step and no undo — treat calling it as sending.' It discloses side effects beyond annotations — the skip-on-roster behavior, the tracking record under data['luma_invites'], and the critical caveat that untracked recipients get invited without a record ('When not_tracked is non-empty, say so in the reply'). No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but well-structured into <summary> and <returns> blocks, front-loading the critical safety and routing information. Every sentence carries distinct value — dedup safety, tracking semantics, the not_tracked warning mandate, and the return dict. It is verbose in places but remains purposeful over several paragraphs of genuinely useful caveats.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description's <returns> section is essential and it delivers — documenting all return fields (event_id, invited, invited_count, skipped, skipped_count, tracked_count, not_tracked, not_tracked_count) plus the conditional note. It clarifies that 'a fully-deduplicated call returns invited_count 0 and is a success, not an error' and explains why tracked_count may be lower than invited_count. For a destructive, side-effecting call, nothing an agent needs to invoke and interpret it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, and the schema descriptions are already informative — emails are 'deduplicated case-insensitively, then filtered against the live roster,' message is capped at 200 chars with 'no per-recipient placeholder substitution,' and event_id 'starts with evt-'. The description adds little parameter-level detail beyond this; the baseline 3 applies since the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence states a specific, unambiguous operation: 'Send Luma's own event invitations to a list of email addresses.' It then deliberately distinguishes itself from its closest siblings — 'Distinct from the LinkedIn/email outreach queues... this is Luma's branded event invite from the calendar the user owns.' An agent can immediately tell it apart from manage_email_outreach_queue, manage_linkedin_invite_queue, and send_email.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit when-to-use and when-not-to-use guidance is present: 'Use the queues to pitch the event to people who don't know it exists; use this to invite people who should land directly on the event's guest list.' It also instructs the agent to 'confirm the recipient list with the user first unless they have already approved this exact wave' and explains when retrying is safe due to deduplication.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

set_linkedin_daily_limitsSet Linkedin Daily LimitsAInspect

limits is a partial {action: cap} map — only the actions you name change, the rest keep their current cap. Each cap is clamped to that action's tier-aware ceiling (the ceiling in check_linkedin_daily_usage's configurable_limits); a cap of 0 or below resets that action to its default. Action keys are the configurable_limits keys (connection_request, message, inmail, …); an unknown key is rejected with the full valid list.

The number the user sets is the day-to-day center, not a fixed count: like Sliq's defaults, each cap carries a small ±jitter so sending doesn't read as automation, so the actual daily number varies by a few around the set value (never above the tier ceiling). Tell the user the value you set, not that they'll send exactly that many.

Owner-only — these are account-level account-safety settings. Returns {success, custom_daily_limits}, the resulting stored cap map. Dict with success and the stored custom_daily_limits map.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitsYesPartial map of action -> daily cap. 0 or below resets that action to default.

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only indicate readOnlyHint=false and destructiveHint=false. The description goes far beyond that by revealing owner-only restrictions, account-safety implications, clamping to tier-aware ceilings, reset-on-zero semantics, rejection of unknown keys, jitter around the set value, and the exact return shape. This is rich behavioral disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but every sentence carries essential operational information. It fronts the action and trigger condition, then explains parameter semantics, nuanced jitter behavior, permission requirements, and return value without fluff. The structure with clear paragraphs makes it easy to scan.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter mutation tool with no output schema, the description is complete: it specifies exactly when to call it, what it does, how the parameter behaves, edge cases, permission restrictions, follow-up verification, and the return format. An agent has everything needed to invoke it correctly and communicate results to the user.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although the input schema already describes the 'limits' parameter with 100% coverage, the description substantially deepens it: it explains partial-map behavior, that unnamed actions keep their caps, clamping to the ceiling from check_linkedin_daily_usage, 0-or-below reset behavior, and that keys come from configurable_limits. This adds real semantic value beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Change the user's per-action LinkedIn daily caps', and immediately ties it to a concrete UI context. It clearly distinguishes this from the sibling read-only tool check_linkedin_daily_usage and from the similar set_linkedin_warmup by focusing on daily per-action limits.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit when-to-use guidance is given: 'Call this when the user asks to raise or lower how many connection requests, messages, InMails, etc. they send per day'. It also tells the agent to actually make the change rather than describe the UI, and to re-read check_linkedin_daily_usage afterward to confirm stored caps, providing clear execution guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

set_linkedin_warmupSet Linkedin WarmupAInspect

disabled=True opts out of the ramp (full daily volume right away — only safe on an already-warm account); disabled=False re-enables it (resumes any active ramp). Owner-only. Returns {success, warmup_disabled, is_ramping_up}, the recomputed ramp state. Dict with success, warmup_disabled, and is_ramping_up.

ParametersJSON Schema
NameRequiredDescriptionDefault
disabledYesTrue to skip warmup (full volume now), False to re-enable the ramp.

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare non-readonly and non-destructive, so the description carries the behavioral burden. It adds meaningful context: it makes a real setting change, has an owner-only restriction, implies a safety condition, and returns the recomputed ramp state. It stops short of describing detailed effects on an active ramp or any side effects, but for a simple boolean toggle this is adequately transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact, front-loads the core purpose, and each sentence earns its place: what it does, when to call it, parameter behavior, and return value. It uses clear short phrases and structured summary/returns tags that are easy to scan.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter, no-output-schema tool, the description is complete: it covers purpose, trigger conditions, parameter semantics, safety caveat, ownership, and return keys. An agent has everything needed to call this tool correctly without external context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already describes the boolean parameter at 100% coverage, giving a baseline of 3. The description adds value by elaborating what True and False concretely do ('full daily volume right away,' 'resumes any active ramp') and by adding the safety condition. This exceeds the schema's plain wording without being redundant.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Turn ... off or on') and resource ('new-account warmup ramp for the user's LinkedIn account'), making the tool's function unmistakable. It also distinguishes this from merely describing the UI ('actually make the change, don't just describe the UI'), which separates it from sibling tools like set_linkedin_daily_limits.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly tells the agent when to call the tool: when the user asks to skip/disable warmup or re-enable it. It also flags 'Owner-only' as a prerequisite and warns that disabling the ramp is 'only safe on an already-warm account,' which is actionable guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

set_outreach_approvalSet Outreach ApprovalAInspect

settings is a partial {subtype: requires_approval} map — True holds the message for the user's approval, False auto-sends. Only the subtypes you name change. Each subtype key names a message type (e.g. email_first_message); an unknown key is rejected with the full valid list.

scope:

  • 'this_agent' (default) — change approval for THIS agent only, leaving the account-wide setting and other agents untouched. Needs an agent in context (or an explicit agent_id). This pins the agent to its own approval settings that no longer follow account-wide changes — tell the user that.

  • 'account' — change the account-wide default that governs every agent with no override of its own. An agent that has its own override is unaffected — warn the user that an account change won't reach any agent that has its own override. Available to the account owner only; when acting for a shared operator, use 'this_agent'.

  • 'inherit_account' — clear THIS agent's own approval settings so it follows the account-wide setting again. settings is ignored.

Returns {success, scope, settings} — settings is the resulting resolved map for the level written. Dict with success, scope, and resulting settings.

ParametersJSON Schema
NameRequiredDescriptionDefault
scopeNoWhich level to change. Defaults to 'this_agent'.this_agent
agent_idNoThe agent to change. Defaults to the agent in context.
settingsNoPartial map of subtype -> requires_approval. Omit for scope='inherit_account'.

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already flag this as a write operation, and the description adds substantial behavioral detail: pinning an agent to its own settings, account changes not propagating to agents with overrides, unknown subtype keys being rejected with the valid list, and inherit_account clearing overrides while ignoring settings. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but structured into summary, returns, and labeled scope bullets, with the core purpose front-loaded. Each sentence carries operational information; even the warnings are necessary for correct agent behavior.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 3-parameter tool with no output schema, the description is unusually complete: it covers when to call, all scope semantics, side effects, error behavior for unknown keys, and the return shape. An agent has everything needed to select and invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although schema coverage is 100%, the description goes far beyond the schema: it defines the partial-map behavior of settings, explains that only named subtypes change, and details the meaning of each scope value including defaults and the interaction with agent_id. This is exactly the semantic context an agent needs.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Change what outbound outreach holds for the user's approval before it sends.' It further clarifies the mutating nature with 'actually make the change, don't just describe the UI,' which clearly separates this setter from read-only siblings like get_outreach_approval.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives an explicit trigger: 'Call this when the user asks to review or auto-send a message type' plus a when-not: 'don't just describe the UI.' Scope conditions are spelled out, including 'when acting for a shared operator, use 'this_agent'' and warnings about account-wide changes not reaching agents with overrides.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

setup_email_sequenceSetup Email SequenceA
Destructive
Inspect

For single personal emails, use draft_email + send_email.

A campaign already sending without a flow keeps calling this tool without node_id. Every other campaign sends through its flow, even a single email: create it with define_sequence and add the prospects with track_prospects, and the flow's send steps call this tool with their node_id. A call without node_id on an agent that has no flow and hasn't sent any outreach yet is refused.

An agent is required — create one via create_agent if none exists. This tool creates tracking items for the recipients it queues, so don't also call track_prospects for them. Only call this AFTER the user has explicitly confirmed the outreach. Dict with queued_count, skipped items, a preview of the first queued email, first_send_at / last_send_at (see below), and tracking_skipped — recipients already tracked in another campaign, so nothing queued for them. Each entry names existing_agent_title / existing_agent_id; surface these and let the user place them.

This tool QUEUES; it never sends. Nothing has been delivered when it returns — a background beat drains each mailbox in derived order at a human-like pace, minutes to days later. first_send_at / last_send_at are ISO timestamps bounding the projected send window (from the read-time forecast), and awaiting_approval counts rows held for the user's approval. Gated rows are excluded from the window; when every row is gated both bounds are null and nothing is projected until the user approves.

So report queued work as queued, with the window: "Queued 4, first lands 12:02pm, last 12:11pm." Do NOT tell the user these were sent.

Writing a marker into a campaign's own tracker (a sheet, a CRM field) at queue time is fine and is usually what its dedup depends on — skip it and the next run re-queues the same people. But that marker records handoff, not delivery: don't cite it back as proof a send happened, and don't let it turn into "sent" in your summary. For what actually went out, read the queue (manage_email_outreach_queue(action='status')); to act at real send time, the agent needs a sent_email trigger.

ParametersJSON Schema
NameRequiredDescriptionDefault
enrichNoWhen True, look up each recipient's LinkedIn URL via Apollo (using their `email` plus optional `first_name` / `last_name`) and stamp it on the tracking row. Costs 1 Sliq credit per verified hit; free with BYO Apollo. Recipients that already carry `linkedin_url`, those missing inputs, and those past the credit limit are silently skipped. Reverse of `setup_linkedin_sequence(enrich=True)`.
mailboxNoEmail address of a connected mailbox (e.g. 'alice@acme.io'). Omit to use the user's default mailbox. When the user has multiple mailboxes connected, ask which to use rather than guessing — surfacing the choice is the agent's job, not a silent fallback. For segmented delivery ("first third from A, second from B, last from C"), call this tool once per segment with the segment's recipients and the segment's mailbox. The tool does not rotate across calls; per-call recipient lists are how segmentation is expressed.
node_idNoOptional. The id of the sequence-DAG node this batch enacts (a node in the agent's `sequence`, authored via `define_sequence`). Stamped on every queued row so the Campaign Flow view can place each prospect and count per node. Pass it whenever the agent has a sequence. Rejected with ModelRetry if it isn't a node in the agent's sequence.
agent_idYesRequired. Agent ID to associate with the queued items.
recipientsYesList of dicts. Required: `email`. Optional per-recipient `subject` / `body` (final literal text; placeholder syntax `{first_name}`, `[name]`, `<<name>>`, `{{name}}` is rejected with ModelRetry; overrides templates). Other fields (`first_name`, `last_name`, `company`, `title`) are stored on the tracking item for later lookup — NOT substituted into templates. Other extras are ignored, except the LinkedIn identifiers: `linkedin_url` is honored from either the top-level key OR a `data` sub-dict (validated / canonicalized before storage), while `linkedin_provider_id` is honored from the top-level key only. Both land on the prospect's LinkedIn fields. For mixed-channel campaigns, also pass `linkedin_url` (and `linkedin_provider_id` when known) on each recipient so this tool can dedup against any existing LinkedIn-keyed tracking row for the same person — both identifiers land on one row. When `find_email` returned `{email, linkedin_url}` for this recipient, forward both fields here verbatim. On a flow email node this is also how a just-discovered address binds to the prospect resting on the node: passing their `linkedin_url` resolves the send to that existing row instead of orphaning a new one.
is_follow_upNoIf True, send as a threaded reply to the original outreach email instead of a new email. Requires recipients to have been previously emailed via this agent.
body_templateNoBatch fallback body for recipients without their own `body`. Final literal text — placeholder syntax rejected. Bodies (this and per-recipient `body`) are Markdown, rendered to HTML at send time for every provider — write hyperlinks as [text](url) so the link reads as its anchor text rather than a raw URL.
subject_templateNoBatch fallback subject for recipients without their own `subject`. Final literal text — placeholder syntax rejected.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With annotations only declaring readOnlyHint=false and destructiveHint=true, the description adds substantial behavioral context beyond them: it QUEUES and never sends, sends drain minutes-to-days later via a background beat, it creates tracking items (so don't double-call track_prospects), dedup/`tracking_skipped` semantics, and awaiting_approval gating that can null the send window. This is exactly the extra context the annotations cannot carry.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is front-loaded and tagged with <summary>/<returns>, which helps, but it runs long and repeats the same point about not reporting queued work as sent three times ('report queued work as queued', 'Do NOT tell the user these were sent', 'don't let it turn into sent'). The returns prose is dense and could be tightened.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, and the description compensates by fully documenting the return shape (queued_count, skipped, preview, first_send_at/last_send_at, tracking_skipped, awaiting_approval) and the queue-vs-send distinction. For an 8-parameter mutation tool with an external prerequisite (agent) and flow interaction, nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds meaning beyond the schema on parameter interplay — most notably when to pass `node_id` (every campaign that sends through a flow) versus the no-flow case, and how recipient `linkedin_url` binds a just-discovered address to an existing prospect row. It does not restate the per-param syntax already documented in the schema, so it earns above baseline without being redundant.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The summary gives a specific verb+resource ('Queue bulk email outreach') and immediately scopes it as a 'Campaign/batch tool' that handles queuing, rate limiting, staggering, business hours, and tracking. It explicitly distinguishes itself from siblings by naming draft_email + send_email for single personal emails and setup_linkedin_sequence as its reverse. An agent can tell this apart from single-send and LinkedIn tools without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit when-to-use routing ('For single personal emails, use draft_email + send_email'), prerequisites ('An agent is required — create one via create_agent if none exists'), and a hard precondition ('Only call this AFTER the user has explicitly confirmed the outreach'). It even describes the flow-based invocation path via define_sequence/track_prospects and when the no-node_id call is refused.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

setup_linkedin_monitoringSetup Linkedin MonitoringAInspect

An agent can host several named monitors. mode='network' watches the user's whole 1st-degree feed; mode='list' watches an explicit set of people — pass members as their LinkedIn profile URLs or vanity slugs; mode='topic' searches all of LinkedIn for posts matching keywords, beyond the user's network. Watching the user's own profile (their URL in members) is how a user gets engager capture on their own posts. writing_instructions steer the comment voice. Kicks off the first run immediately, then runs on run_weekdays (default Mon/Thu, in the user's timezone). That first run covers only the members present at creation, and edit_monitor_members starts no run — so create a list monitor with its complete membership in this one call (assemble a large or query-derived list in run_code and pass the whole variable), since members added afterward miss the backlog their first scheduled run would have caught. Comments land in the approval queue by default — they post without review only if the user has enabled auto-approve for the linkedin_comment subtype.

list mode works even when the user hasn't connected LinkedIn: discovery is cookieless and drafting never touches their session, so drafted comments hold in the approval queue and post once they connect. network/topic discover through the user's own LinkedIn session, so those two need it connected first.

Schedule and recency are per-monitor. run_weekdays is which days the monitor runs — weekday ints Mon=0 … Sun=6 in the user's local timezone (e.g. [3] = Thursdays only, [0,1,2,3,4] = every weekday). recency_days is how fresh a post must be to earn a drafted comment: only posts published within that many days are commented on (default 7, max 7). Set these when the user asks for a cadence ("once a week on Thursdays") or freshness rule ("only comment on posts from the last 3 days") — those are the knobs, not the agent goal text. A recency_days shorter than the gap between runs deliberately skips the older posts in that gap: narrow it for a user who wants only the freshest posts, or to trim a list monitor's per-post cost (see Cost — a shorter window pulls fewer posts, though the per-profile floor dominates a large list's cost). Leave it at the default week to comment on everything since the last run. Omitting run_weekdays, recency_days, or (on a list monitor) members on a re-setup keeps the monitor's current value; the other fields overwrite on every re-setup, so re-pass writing_instructions/draft_comments/fetch_engagers/engager_icp/ comment_scope/engager_instructions when editing rather than dropping them. For membership tweaks (add or drop a few people) use edit_monitor_members — it takes only the changes, so you never re-echo the whole list (dropping a URL on the re-echo silently stops watching that person).

Cost: a queued drafted comment costs 1 credit (all modes). list mode additionally bills for the paid cookieless scraper's per-event usage (1 credit = $0.10): 0.04 credit per post it returns plus 0.02 credit per watched profile that posted nothing in the window — every watched profile is billed on every scheduled run whether or not it posted, so the per-profile floor (not the per-post charge) dominates a large list. That floor is a hard minimum: a run costs at least 0.02 × number of watched profiles (a silent profile exactly that, a posting one more), so a 2,000-profile list is ≥ 40 credits every scheduled day. Because that recurs per run, recommend a reduced cadence for a large list: the default is Mon/Thu, and for lists in the high hundreds or more, suggest weekly (run_weekdays=[0]). Other levers are trimming the member list; narrowing recency_days only trims the smaller per-post component. Flag this to the user when setting up a list monitor, especially a big one; a zero-balance list monitor won't run at all (even for engager capture). network/topic discovery is free (Unipile session).

fetch_engagers has two optional companions, both handed to the woken new_post_engagers run on the event. engager_icp filters WHO is collected (ICP, titles, exclusions); blank collects everyone. engager_instructions decides WHAT HAPPENS to them; blank means they are recorded into the agent's Output list and nothing else is done. Capture never sends outreach on its own — if the user wants a sequence or a connection request, it has to be spelled out in engager_instructions.

keywords is a LinkedIn boolean query. Combine topics with OR and quote multi-word phrases: "creator economy" OR "influencer marketing". AND narrows, NOT excludes, and parentheses group: ("seed" OR "series a") AND fundraising. Operators must be UPPERCASE (lowercase and/or/not are read as literal words). The combined AND+OR count is capped by the user's LinkedIn plan (free/premium 5, Sales Navigator 15, Recruiter unlimited; NOT doesn't count) — past the cap LinkedIn silently returns nothing, so cover many topics with several monitors each within the cap, not one giant OR. A single unquoted topic (fundraising) is fine. A malformed query (unbalanced quotes/parens, dangling operators) is rejected.

For network, leave keywords empty in almost all cases — the feed is already ranked and the per-post draft step skips anything not worth commenting on; set a narrow query only when the user wants one specific slice. list mode ignores keywords — it watches the named people's recent posts directly, and the draft step is the relevance gate. For mode='topic', keywords is required and is the only filter.

Reusing an existing name updates that monitor (and re-activates it if it was stopped) — this is also how you edit one. Prefer edit_monitor_members for a list monitor's membership; reserve setup's members for the monitor's full initial membership or a deliberate full reset. Dict with status ('active' or 'error'), name, message, and additional keys.

ParametersJSON Schema
NameRequiredDescriptionDefault
modeYes'network' (1st-degree feed), 'list' (explicit people), or 'topic' (keyword search across all of LinkedIn).
nameYesshort label for this monitor (e.g. 'founders-i-follow').
membersNolist mode only — LinkedIn profile URLs/slugs to watch. Required when creating a list monitor; omit on a re-setup of an existing list monitor to keep its current members (use `edit_monitor_members` to add or drop individuals).
agent_idYesThe agent this monitor lives on.
keywordsNoa LinkedIn boolean query — required for topic mode, an optional relevance narrowing for network, ignored for list. Combine topics with OR (`"a" OR "b"`), AND to narrow, NOT to exclude; the AND+OR count is capped by the user's plan (see the keyword note above).
engager_icpNooptional — targeting criteria the collected engagers are filtered by (ICP, titles, exclusions). Blank collects everyone. Only meaningful when fetch_engagers is on.
recency_daysNoonly comment on posts published within this many days (1–7, default 7). Omit to keep the current value.
run_weekdaysNowhich weekdays the monitor runs — ints Mon=0 … Sun=6 in the user's timezone (e.g. [3] = Thursdays only). Omit to keep the current value (default Mon/Thu on a new monitor).
comment_scopeNooptional — which post types are worth a drafted comment, in the user's words. Blank keeps the built-in criteria only.
draft_commentsNoqueue a drafted comment per discovered post.
fetch_engagersNocapture each post's engagers and wake this agent when new people engage.
engager_instructionsNooptional — what to do with captured engagers. Blank means collect them into the agent's Output list and take no action.
writing_instructionsNooptional voice/style guidance for the drafts.

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only say readOnlyHint=false and destructiveHint=false secret; the description carries the full behavioral burden and exceeds it. It discloses that the first run starts immediately, that reusing a name updates and re-activates a monitor, that list discovery is cookieless, that auto-approval governs whether comments post, that a zero-balance list monitor won't run, and the full credit-cost model.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every sentence carries operational value; it is front-loaded with a summary and high-level behavior. It loses one point because the density and long paragraphs make it harder to scan quickly for an agent deciding among 13 parameters, and it could benefit from headings or bullets around modes, cost, and update semantics.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex tool with 13 parameters, no output schema, and multiple modes, this description is remarkably complete. It covers mode differences, connection prerequisites, schedule and recency behavior, cost with a concrete example, update semantics, keyword query grammar, engager event behavior, and cross-tool routing. Nothing necessary for correct invocation is left to guesswork.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although schemas coverage is 100%, the description adds substantial meaning beyond the property descriptions: keyword boolean syntax and plan caps, recency_days max and cost implications, run_weekdays timezone rules, members re-setup semantics, and what blank engager_instructions does. This is precisely the guidance an agent needs to set parameters correctly.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Set up recurring LinkedIn post monitoring on an agent.' It immediately distinguishes itself from related tools like edit_monitor_members and stop_linkedin_monitoring by describing what setup does versus membership tweaks and by naming the sibling explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit when-to-use and when-not-to-use guidance: use edit_monitor_members for membership tweaks, use setup's members only for initial membership or full reset, and it details mode-specific choice criteria for network/list/topic. It also states when LinkedIn must be connected versus when list mode works cookieless.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

setup_linkedin_sequenceSetup Linkedin SequenceA
Destructive
Inspect

A campaign already sending without a flow keeps calling this tool without node_id, as do a one-off send outside a campaign and a resolve look-up. Every other campaign sends through its flow, even a single message or connection requests with nothing after: create it with define_sequence and add the prospects with track_prospects, and the flow's steps call this tool with their node_id. A call without node_id on an agent that has no flow and hasn't sent any outreach yet is refused.

Only call this AFTER the user has explicitly confirmed the outreach.

BATCH: pass ALL recipients in a single call. The tool is designed to batch — provider_id recipients queue directly (resolving before send unless already verified) and URL/slug recipients route through the background resolving queue with rate-limited, business-hours pacing. Calling once per recipient multiplies prompt overhead by ~N and produces no observable benefit. If you have 27 prospects to queue, that's ONE call with identifiers of length 27, not 27 calls of length 1. Dict with known_queued (provider_id recipients queued directly), deferred_count (URL/slug recipients queued for background resolution), tracking_skipped (recipients already tracked in another campaign, so nothing queued for them — each entry names existing_agent_title / existing_agent_id; surface these and let the user place them), skipped (recipients the queue helper refused — each entry has target_name, provider_id, reason, error, plus existing_agent_id / existing_agent_title when a live row on another campaign is what blocked the send; name that campaign to the user rather than calling it a duplicate on this one), accept_followup (present only when connection requests queued to this agent but no sequence node or accept trigger will fire a message on acceptance — names how to wire one), and rate_limit_estimate — an ETA for the batch covering expected profile resolution and sending days given the user's current rate-limit budget.

ParametersJSON Schema
NameRequiredDescriptionDefault
enrichNoWhen True, look up each prospect's verified work email via Apollo and stamp it on the tracking row. Requires each recipient to have `target_name` plus a URL-shaped `linkedin_url`. Costs 1 Sliq credit per verified hit; free with BYO Apollo. Recipients missing inputs and recipients past the credit limit are silently skipped.
notifyNoOnly for action_type='resolve'. False (default) = a silent data lookup the person is NOT notified of. True = a visible "View Profile" visit that DOES notify them ("X viewed your profile") — a no-message warm-up touch, typically placed later in a sequence, not a data read. Same paced lookup and daily cap either way. Rejected on non-resolve actions. On a sequenced call the resolve node's own `notify` field is authoritative and is OR'd in, so a "View Profile" node always visits.
node_idNoOptional. The id of the sequence-DAG node this batch enacts (a node in the agent's `sequence`, authored via `define_sequence`). Stamped on every queued row so the Campaign Flow view can place each prospect and count per node. Pass it whenever the agent has a sequence. Rejected with ModelRetry if it isn't a node in the agent's sequence.
agent_idNoOptional. Pass for campaigns or when inside an existing agent. Omit for ad hoc sends — the tool auto-uses the "LinkedIn Quick Actions" default agent. Every send lands in the agent you name; recipients already tracked in a different one are reported in `tracking_skipped` rather than queued (see Returns).
action_typeYes'resolve', 'connection_request', 'message', 'inmail', or 'follow'. For 'resolve' and 'follow', the `message` field is unused and rejected with ModelRetry. Follow auto-sends by default (its own `linkedin_follow` approval subtype); gate it via outbound-approval settings. 'resolve' commits NO outreach — it queues a paced profile lookup that fires a `resolved_profile` trigger event per person; use it when the per-person action depends on looked-up data (see the trigger-code skill for the branching example). Pair with `notify=True` for a visible "View Profile" visit (see `notify`). The event carries the looked-up profile — `already_connected`, `network_distance`, `connections_count`, `follower_count`, `headline`, `title`, `company` — and the same fields land on the prospect's row under `data`, so a later turn reads them with `query_prospects` instead of resolving again. A connection-count rule can read `connections_count` from here; when the rule turns on the count alone, `enrich_linkedin_profiles` returns it for the whole list in one call without spending account budget, so filter there first and queue only the recipients that pass.
identifiersYesList of recipient dicts. Each dict must carry one of `linkedin_url` (a profile URL or slug), `provider_id`, or `chat_id`, copied verbatim from the tool result that surfaced the person — never a slug rebuilt from a display name, which enrolls the wrong person. Freeform attributes go under `data`; unknown top-level keys are rejected. If you have no identifier for someone, omit them rather than guess. When a recipient's `target_name` shares no tokens with any LinkedIn name already held for that `provider_id` (a verified prospect or a post engager), the whole batch is rejected with ModelRetry before anything queues — fix the pairing and re-send (use the person's LinkedIn URL/identifier if the pid is wrong; use one consistent name if it's the same person). A pid with no held name isn't gated here — it resolves before send and the resolver adjudicates the name then.
linkedin_apiNoFor inmail only. Leave unset — the tool bills the InMail against the pool the account's LinkedIn plan provides. Only pass an explicit 'classic', 'sales_navigator', or 'recruiter' to force a pool.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only give readOnlyHint=false / destructiveHint=true; the description goes far beyond by disclosing batching semantics, background 'resolving' queue with business-hours pacing, ModelRetry gates on name mismatch, silent skipping of enrich recipients past credit limits, `resolve` committing no outreach, and the accept-followup warning. This is unusually rich behavioral context for a destructive mutation tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well structured with summary/returns tags and front-loaded guidance, but the returns prose is lengthy and some sentences could be trimmed. Nearly all content earns its place given there is no output schema, though the density borders on verbose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 7-parameter destructive tool with no output schema, the description fully covers usage routing, parameter interaction, failure modes, and a detailed returns breakdown (known_queued, deferred_count, tracking_skipped, skipped, accept_followup, rate_limit_estimate). Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3, but the description adds cross-cutting guidance the schema can't: 'pass ALL recipients in a single call' with the explicit 27-in-one-call example, when node_id must be passed, and the rationale for never rebuilding linkedin_url from a display name. It meaningfully supplements rather than repeats the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Queue LinkedIn outreach (connection requests, messages, or InMails)') and explicitly positions itself as 'The single LinkedIn outreach tool,' distinguishing it from siblings like manage_linkedin_invite_queue and track_prospects. Lists the automatic behaviors (profile resolution, connection-status checking, rate limiting, staggering, tracking creation) so an agent knows exactly what this call covers.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly enumerates when to call without node_id (campaign already sending, one-off send, resolve lookup) versus when to route through a flow (create with define_sequence, add via track_prospects). Adds a hard precondition — 'Only call this AFTER the user has explicitly confirmed the outreach' — and a refusal case for no-flow agents that haven't sent outreach.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

setup_social_listeningSetup Social ListeningAInspect

channels is the full desired channel list (supported: 'reddit', 'hacker_news'). search_queries is broad alert terms grouped by channel, e.g. {'reddit': ['dripify', 'linkedin'], 'hacker_news': ['outbound']} — keep terms to 1-3 words; a cheap relevance filter rejects noise, so broad terms are intentional. A channel in channels with no terms here falls back to auto-generated terms from search_spec. reddit_subreddits scopes Reddit alerts to specific subreddits (e.g. ['r/startups', 'r/sales']); pass [] to watch all of Reddit. search_spec is the ICP / problem description the relevance filter matches against. writing_instructions steer the draft-reply voice.

Changing channels, search_queries, or reddit_subreddits reconciles the backend alerts immediately — unchanged terms stay active, removed terms stop matching, added terms start matching. Editing only search_spec or writing_instructions does not touch the alerts. Future matching posts/comments arrive through webhooks; there is no historical backfill. Dict with success, agent_id, channels, search_queries, and reddit_subreddits.

ParametersJSON Schema
NameRequiredDescriptionDefault
agent_idYesThe agent this config lives on.
channelsNofull desired channel list ('reddit' and/or 'hacker_news').
search_specNoICP / problem description used for relevance filtering.
search_queriesNo{channel: [1-3 word alert terms]}.
reddit_subredditsNosubreddit scope for Reddit ([] = all of Reddit).
writing_instructionsNooptional voice/style guidance for the drafts.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only show readOnlyHint=false and destructiveHint=false, so the description carries the behavioral burden. It discloses partial-update semantics, immediate reconciliation of channel/query/subreddit changes, that search_spec and writing_instructions edits do not touch alerts, and that no historical backfill is performed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but well-organized, front-loading the core purpose and then detailing each parameter and side-effect. It is somewhat long, but the length is justified by the number of parameters and the nuanced reconciliation behavior.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a configuration tool with no output schema, the description is remarkably complete: it explains return values, side effects, alert reconciliation, no backfill, and fallback behavior. An agent has enough context to invoke the tool correctly and set expectations for the result.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, but the description adds substantial meaning beyond field names: examples for search_queries, fallback behavior for channels, subreddit scoping with [] meaning all of Reddit, and guidance that alert terms should be 1-3 words because a relevance filter rejects noise. This goes well beyond the baseline set by the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Set up or edit an agent's social-listening config' for Reddit/Hacker News alert monitoring. This clearly distinguishes it from sibling tools like setup_linkedin_monitoring, which target a different domain.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context for when to use the tool: to create or update social-listening configuration, with partial-update semantics. It does not explicitly name alternatives or say when not to use it, but the intended usage is unambiguous from the examples and behavior details.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

stop_linkedin_monitoringStop Linkedin MonitoringAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
nameYesThe monitor to stop (the label it was set up with).
agent_idYesThe agent the monitor lives on.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations give readOnlyHint=false and destructiveHint=false, so the description is expected to convey behavioral traits. It adds key behavioral details: scheduled runs stop, discovered posts and queued comments are retained, and the monitor can be resumed with the same name. This goes beyond the annotations and provides meaningful side-effect context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary is compact, front-loaded with the core action, then covers retention and resumption in two short sentences. A separate returns section clearly states the return shape. Every sentence contributes value and there is no redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple two-parameter tool with no output schema, the description is complete: it declares what happens, what is preserved, how to reverse the action, and what the response contains. There are no obvious gaps for an agent to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already defines both parameters fully. The description adds useful context like 'same name to resume' and 'label it was set up with,' but this mostly reinforces rather than adds substantial new meaning beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action: stop a LinkedIn post monitor and prevent future scheduled runs. It also clarifies what is preserved and how to resume via setup_linkedin_monitoring, clearly distinguishing it from related monitoring tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explains when to use the tool: to stop a LinkedIn post monitor that should no longer run. It explicitly points to setup_linkedin_monitoring as the way to resume, giving the agent a clear alternative path. It does not discuss other alternatives like get_linkedin_monitors, but the usage context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

track_monitored_companiesTrack Monitored CompaniesAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
itemsYesList of dicts, each with: - identifier: The company's display name (canonical) or a TLD-shape domain (e.g. "stripe.com") — REQUIRED - display_name: Human-readable company name (optional; falls back to identifier) - stage: 'active' (default) or 'removed' - data: Dict of company attributes — website, raw_input, etc. (optional)
agent_idYesID of the agent to add companies to

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already signal a non-read-only, non-destructive write, and the description adds meaningful behavior beyond that: cross-agent dedup, the nightly sweep over active rows, and active/removed semantics via the stage field. It also discloses the return shape with created/updated/skipped counts. This is a useful, non-contradictory behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and well-structured: a summary sentence followed by a returns note. It front-loads the action, includes the meaningful dedup and nightly-sweep context, and does not repeat schema details or add fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has only two required parameters, both fully described in the schema, and the description covers the activation behavior and returns summary despite no output schema. Minor omissions like expected permissions or edge cases around duplicate items are not critical, so the definition is nearly complete for correct selection and invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the items parameter is already well-documented with identifier, display_name, stage, and data. The description does not add new parameter-level meaning; it only restates the bulk-add purpose and return counts. Baseline 3 applies because the schema carries the parameter burden fully.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action and resource: 'Bulk-add companies to an agent's monitoring list.' It also clarifies scope with 'cross-agent dedup' and the activation effect on the nightly sweep. This clearly differentiates it from update/edit/sibling tools by emphasizing bulk-adding rather than modifying or querying.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description conveys when it applies by saying adding companies here turns monitoring on, which implies the use case. However, it never mentions alternatives such as edit_monitor_members, update_monitored_company, or query_monitored_companies, and gives no 'when not to use' guidance. The usage context is clear but not explicitly routed against siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

track_prospectsTrack ProspectsAInspect

A person belongs to exactly one campaign, so someone already tracked in another campaign is reported in skipped rather than added twice; pass move_existing=True to move them into this agent instead. A durable contact left without a campaign (its campaign was deleted) is instead adopted straight in and reported in adopted. Someone already on THIS campaign whom the user stopped pursuing (a 'skipped' stage) is reported in removed, not re-enrolled and not sent to — clear the skip with update_prospect first if the user explicitly wants them back.

On an agent with a define_sequence flow, tracking IS enrollment — the flow fires each prospect's first touch itself (see the returned flow field); never queue a first send for prospects you just tracked. Dict with created and updated counts, and moved, adopted, removed and skipped details

ParametersJSON Schema
NameRequiredDescriptionDefault
itemsYesPeople to add (see ProspectItem). Each carries a `person` with the identifiers you already hold (LinkedIn URL / slug, email, or provider_id) copied verbatim from the tool result that surfaced them — never a slug rebuilt from a display name, which resolves to the wrong person. Junk like a company URL or 'N/A' is rejected. The display name goes on `person.display_name`, taken from the same row as the identifier — a name that shares no token with the LinkedIn name already on file for its ID rejects the whole call. Optional freeform `data` per item.
agent_idYesID of the agent to add prospects to
move_existingNoMove people tracked in another campaign into this one instead of skipping them. Destructive — it cancels whatever was still queued for them, so only pass it on an explicit user request.

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations are only readOnlyHint=false and destructiveHint=false, which are generic. The description compensates thoroughly: it discloses that move_existing is destructive, explains the skipped/adopted/removed semantics, and clarifies the define_sequence enrollment behavior. It also notes the returned flow field. No contradiction with annotations; instead it adds rich behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Although long, every sentence earns its place. The summary is front-loaded with the core purpose, followed by structured paragraphs covering dedup behavior, edge cases, and the sequence flow. No fluff; the density is justified by the complexity of the tool. It is well-organized for an agent to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers all important scenarios: cross-agent dedup, within-campaign skip stages, adopted orphans, removed entries, the destructive move_existing, and the define_sequence enrollment nuance. It also provides a returns tag summarizing counts for created/updated/moved/adopted/removed/skipped. With no output schema, this high-level return description is sufficient. Nothing critical is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, but the description adds significant semantics beyond the schema: it details the person identifier requirements (copy verbatim, never rebuild slug, reject company URL/N/A), the data field signal usage ('Hiring for Founding AE'), and move_existing's destructive effect. This goes well beyond what the schema provides, making parameter usage crystal clear.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a clear, specific statement: 'Bulk-add people (prospects) to an agent's outreach list with cross-agent dedup.' This names the exact verb, resource, and a key feature (dedup). It also distinguishes itself from sibling track_monitored_companies by explicitly directing to that tool for companies, so there is no ambiguity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives direct usage guidance: 'For companies to monitor, call track_monitored_companies instead.' It explains when to pass move_existing ('on an explicit user request'), the behavior for skipped/adopted/removed prospects, and the important rule about the define_sequence flow (tracking IS enrollment, never queue a first send). This is explicit and actionable, leaving no inference to the agent.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_agentUpdate AgentAInspect

An agent's recurring runs are steered by each trigger's prompt and code; the goal is shared context, not the run script. A goal edit that adds, removes, or changes a recurring step therefore doesn't reach the runs on its own: after updating the goal, read the agent's triggers and bring each affected trigger's prompt or code in line via update_trigger — or confirm the existing triggers already cover the change. Narrowing a live outreach campaign's ICP is the same shape with more surfaces: segment-bound triggers, per-segment templates, and queued-unsent sends stay wired to the dropped segment; follow your channel's outreach skill to reconcile them.

Set priority=True to push this whole campaign's queued sends — email and LinkedIn alike — ahead of your other campaigns'. It starves the others until it drains — that's intended. priority=False clears it. Dict with success status and updated agent details

ParametersJSON Schema
NameRequiredDescriptionDefault
goalNoNew goal/instructions. Name any outside resource a run reads (a Google Sheet, doc, file, URL) by the identifier its tool takes (the spreadsheet ID and tab, the URL), not only by its title — a later run doesn't see this chat.
titleNoNew title
statusNoSet to 'active' or 'paused'. Pausing stops the whole agent — every trigger on it.
agent_idYesID of the agent to update
priorityNoTrue to prioritize this campaign's queued sends (email and LinkedIn) over other campaigns; False to clear.

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=false and destructiveHint=false, so the description carries the burden of behavioral disclosure — and it delivers substantially. It explains partial-update semantics ('Only provided fields are changed'), the critical goal-vs-trigger propagation nuance (a goal edit doesn't reach runs on its own), and the priority starvation behavior with its intent ('It starves the others until it drains — that's intended'). No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with a clear purpose sentence and structured into <summary>/<returns> blocks, but the middle section on goal edits, ICP narrowing, and outreach reconciliation is long and dense — arguably more than an agent needs to invoke the tool correctly, and partly duplicative of the schema's priority note. Informative but not lean.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with 5 parameters and genuinely complex side effects (trigger propagation, priority starvation, confirmation rules), the description is remarkably complete. The trigger-reconciliation guidance is operational context an agent needs. The minimal <returns> note is acceptable given no output schema exists. Slightly more than needed for correctness but not missing anything essential.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema documents all five parameters thoroughly (goal's resource-identifier caveat, status's pause-every-trigger effect, priority's semantics). The description adds a global partial-update behavior note but mostly restates what the schema already carries. Baseline 3 is correct given the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The summary states a specific verb (update), resource (agent), and the exact mutable fields (title, goal, active/paused status, send priority). This clearly distinguishes update_agent from sibling tools like update_trigger, update_node, and the create/delete agent pair — an agent can tell them apart without opening any schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes to the alternative when needed: 'bring each affected trigger's prompt or code in line via update_trigger'. It also gives context-sensitive authorization guidance (confirm with user in interactive sessions; background runs may refine their own goal without confirmation). This is concrete, actionable when-to-use guidance naming a specific sibling.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_calendar_eventUpdate Calendar EventA
Destructive
Inspect
ParametersJSON Schema
NameRequiredDescriptionDefault
bodyNoNew event description/notes (only if changing)
subjectNoNew event title (only if changing)
locationNoNew event location (only if changing)
attendeesNoNew attendee list. Each is an object with required "email" and optional "name". (only if changing)
end_datetimeNoNew end time as ISO 8601 in the user's LOCAL timezone, not UTC (e.g., "2026-03-16T12:00:00"). Do not include a timezone offset or Z suffix.
start_datetimeNoNew start time as ISO 8601 in the user's LOCAL timezone, not UTC (e.g., "2026-03-16T11:30:00"). Do not include a timezone offset or Z suffix.
calendar_event_idYesThe database id of the calendar event (from search_calendar results)

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations mark destructiveHint=true, and the description reinforces this by requiring explicit user confirmation before the call. It also discloses the return shape ('Dict with success status and updated event details'), adding useful behavioral context beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact, front-loaded with the core action, and structured with a summary and a returns note. Every sentence adds practical information: what it updates, the prerequisite lookup, and the mandatory confirmation step.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the fully described schema, the destructive annotation, and the clear workflow, the description covers the key aspects needed to call the tool correctly. The return note, though brief, is sufficient for invocation since no output schema is provided.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the parameter descriptions already explain semantics like 'only if changing' and the local-timezone datetime rule. The description adds no significant parameter-level meaning beyond the schema, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb and resource: 'Update an existing calendar event' and specifies the backends ('Outlook or Google Calendar'). It also implicitly differentiates from create_calendar_event by emphasizing 'existing' and the need to search first.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives an explicit workflow: use search_calendar first, pass the retrieved id, and always ask for explicit confirmation before calling. It does not enumerate when-not-to-use scenarios, but the context and prerequisite are clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_crm_taskUpdate Crm TaskAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
clearNoFields to empty: 'due_date', 'person', 'company', 'deal'.
notesNoReplace the notes entirely. To add to them, use notes_append instead.
titleNoNew title.
statusNo'done' to complete it, 'open' to reopen it.
deal_idNoLink this deal instead (query_deals `id`).
task_idYesThe task's id (query_crm_tasks).
archivedNotrue deletes the task (reversible), false restores it. A deleted task can only be changed in a call that also restores it (archived=false).
due_dateNoNew due day, YYYY-MM-DD.
person_idNoLink this person instead (query_people `id`).
company_idNoLink this company instead (query_companies `id`).
notes_appendNoAdd this as a new paragraph at the end of the notes.
assignee_emailNoReassign to this active teammate's email from list_teammates (not an invited one).

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses important behavioral details beyond the annotations: deletion is a reversible archive ('archived=true hides the task but keeps it'), archived=false restores it, and the return value matches a query_crm_tasks row. It also warns about the follow-up suggestions limitation, giving the agent accurate side-effect expectations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary is a compact paragraph that front-loads the verb and action list, then adds only high-value caveats: id source, reversible delete behavior, and the follow-up limitation. The returns block is minimal and useful. There is no filler or repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 12-parameter mutation tool with no output schema, the description plus schema covers everything an agent needs: supported operations, id source, soft-delete behavior, return shape, and an explicit limitation. It is complete enough to invoke correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and each parameter already has rich descriptions, such as notes vs notes_append, archived semantics, and assignee_email sourcing. The description's summary does not add per-parameter detail, but that is unnecessary given the schema's completeness, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with 'Change a CRM task' and enumerates the full set of supported operations: mark done/reopen, reschedule, rename, reassign, edit notes/links, and delete/restore. This makes the tool's scope unambiguous and clearly distinguishes it from siblings like create_crm_task and query_crm_tasks.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly tells the agent to get the id from query_crm_tasks and states a clear when-not: follow-up suggestions the user hasn't added cannot be changed here. It does not explicitly name alternative tools such as create_crm_task for creation, but the context is still sufficient for correct usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_dealUpdate DealAInspect

Won and lost are statuses, not stages: use status='won' / 'lost', and status='open' to reopen. Deleting (archived=true) hides the deal everywhere but keeps it, so archived=false brings it back. The updated deal, same shape as a query_deals row.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameNoNew name.
clearNoFields to empty: 'amount', 'expected_close_date', 'person', 'company'. A deal must keep a person or a company.
notesNoReplace the notes entirely. To add to them, use notes_append instead.
stageNoMove to this stage, by name or id.
amountNoNew value as a plain number, e.g. 15000.
statusNo'open', 'won', or 'lost'.
deal_idYesThe deal's id (query_deals).
archivedNotrue deletes the deal (reversible), false restores it. A deleted deal can only be changed in a call that also restores it (archived=false).
person_idNoLink this person instead (query_people `id`).
company_idNoLink this company instead (query_companies `id`).
notes_appendNoAdd this as a new paragraph at the end of the notes.
assignee_emailNoNew owner, an active teammate's email from list_teammates (not an invited one).
expected_close_dateNoYYYY-MM-DD.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With only readOnlyHint=false and destructiveHint=false in annotations, the description adds important behavioral context: deletion via archived=true is reversible and hides the deal, archived=false restores it, and a deleted deal can only be changed when also restored. It also explains the won/lost/status distinction and the returned shape.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary is compact and front-loaded with an action list, followed by two crucial semantic clarifications and a one-line returns note. Every sentence earns its place and no schema details are repeated.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 13-parameter mutation tool with no output schema and minimal annotations, the description covers the key pitfalls, the reversible deletion model, and the output format. Field-level detail is fully handled by the schema, so an agent has sufficient information to call the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

All 13 parameters have schema descriptions, so the baseline is 3. The description adds value beyond the schema by disambiguating status vs stage, explaining archive/restore behavior, and linking high-level actions to parameter usage. It does not restate syntax, which is appropriately left to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with 'Change a deal' and enumerates the concrete operations: rename, move stage, mark won/lost/reopen, change amount, close date, owner, notes, contact/company, and delete/restore. This clearly distinguishes it from read tools like query_deals and creation tools like create_deal.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives clear context and a prerequisite ('Get the id from query_deals') and explicitly clarifies that won/lost are statuses, not stages, preventing a common misuse. It does not explicitly state when to choose create_deal vs update_deal, but the modification scope is clear enough.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_message_tagUpdate Message TagAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
nameNoNew name; omit to leave unchanged.
tag_idYesThe tag to edit (from a group's tags list).
descriptionNoNew description; omit to leave unchanged.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The key disclosure — 'A tag's description is what the classifier reads, so re-tag... for the change to take effect' — communicates a non-obvious side effect: editing the description alone does not affect classifier behavior until re-classification. Annotations only declare readOnlyHint=false and destructiveHint=false; the description carries the meaningful behavioral burden and does so well. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary is three tight sentences with zero waste, front-loading the core purpose before the caveat. The returns note is a compact, useful add-on. Every sentence earns its place and the structure is easily scannable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a low-complexity mutation with a fully documented schema, the description covers everything essential: purpose, partial-update semantics, the critical post-edit re-classification requirement, and the return value shape ({id, name, description, created_by}). No output schema exists, but the description compensates by listing the return fields. Nothing material is missing for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% — each parameter already carries its own description ('New name; omit to leave unchanged', etc.). The description adds only marginal reinforcement via 'Only the fields you pass change,' which restates the schema's omission semantics without adding new syntactic nuance. This matches the baseline 3 for fully-covered schemas.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb + resource ('Rename a tag or change its description') and names the exact editable fields. This distinguishes it clearly from siblings like delete_message_tag (destruction) and update_message_tag_group (group-level editing), so an agent can tell them apart without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It provides clear operational context: 'Only the fields you pass change' clarifies partial-update semantics, and the re-tag (classify with force=True) instruction tells the agent what follow-up is required after a description edit. It does not explicitly name alternative tools or state when-not-to-use, so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_message_tag_groupUpdate Message Tag GroupAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
modeNo'single' or 'multi'; omit to leave unchanged.
nameNoNew name; omit to leave unchanged.
criteriaNoThe classify scope to track — {channel, agent_ids, positions, senders}, each null = all (see create_message_tag_group). A provided object REPLACES every axis (an axis you leave out becomes all), so pass the current value of any axis you keep; omit the object to leave the stored criteria unchanged. classify reuses this scope, so change it to re-target what the question tracks.
group_idYesThe tag group to edit (from list_message_tag_groups).
descriptionNoNew one-line description; omit to leave unchanged.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already mark the operation as non-read-only and non-destructive, and the description adds meaningful behavioral detail: partial-update semantics ('Only the fields you pass change') and the important side-effect absence ('Editing does not re-classify anything'). It also documents the return dict, going beyond the structured annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two compact XML blocks with no filler; the key partial-update and no-reclassification semantics are front-loaded before the return shape. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 5-parameter mutation tool with no output schema, the description is complete: it states editable fields, partial-update behavior, absence of reclassification, the follow-up tool, and the return shape. The rich schema covers the remaining parameter details.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema carries the parameter documentation. The description adds only the generic partial-update rule, while the schema already repeats 'omit to leave unchanged' for each field and explains criteria-replacement semantics in detail.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The summary names a specific operation — rename a tag group or change its description, mode, or classify scope — with precise resource and fields. It also distinguishes behavior from the classify sibling by noting that editing does not re-classify anything and pointing to classify_message_tag_group as the follow-up.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides clear context: only passed fields change, and if re-tagging is desired the agent should call classify_message_tag_group(force=True). It does not explicitly state when to prefer this over update_message_tag or create_message_tag_group, so alternatives are not fully enumerated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_monitored_companyUpdate Monitored CompanyAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
stageNo'active' or 'removed' (optional)
agent_idYesID of the agent
identifierYesThe company's display name or domain.
data_updatesNoDict to merge into existing data (optional)

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already indicate this is a write operation and not destructive. The description adds meaningful behavioral context beyond that: the effect of 'removed' on the nightly monitoring sweep and the ability to resume with 'active'. It does not exhaustively detail side effects, but the core behavior is transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely tight: one summary sentence covers purpose and the key behavioral nuance, and a one-line returns note covers output. No filler or repeated schema content; every sentence earns its place and the critical behavior is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the 100% schema coverage, the provided annotations, and the simple parameter set, the description is complete enough for correct invocation. It clarifies the stage lifecycle, notes the return type, and names the two stage values that matter. No critical information is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description adds value by linking the stage parameter to real-world consequences ('removed' stops monitoring, 'active' resumes it), which enriches the raw enum values already present in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Update'), a clear resource ('a single monitored company'), and the exact scope ('stage and/or data'). This distinguishes it from related monitoring siblings like query_monitored_companies and track_monitored_companies, which read or track rather than mutate.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives concrete usage instructions for the pivotal stage values: setting stage='removed' to stop the nightly sweep and 'active' to resume. This is clear, actionable context, though it does not explicitly name sibling alternatives or state when not to use the tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_nodeUpdate NodeAInspect

Because no node is added, dropped, or renamed, every prospect keeps its cursor and each node keeps its on_enter enactment (prompt, code, run history). The edited sequence is re-validated as a whole, so an edge repoint that would dangle a reference, orphan a node, or introduce a cycle is rejected rather than saved. To add or remove nodes, change a node's kind, or edit the start node, re-author the flow with define_sequence instead.

A forked-on condition is often restated in several places — a decision's rule, each arm.case, a downstream terminal's label, and the node's on_enter prompt. When you change one, change the others in the same turn (a further update_node for the node fields, update_trigger for the prompt) so they agree. Dict with success, agent_id, and node_id. Read the updated flow via get_campaign_flow.

ParametersJSON Schema
NameRequiredDescriptionDefault
nodeYesThe node's complete new definition — an omitted optional slot (a `follow_up`, a `timeout`) is dropped, not preserved, so carry forward every field the node keeps. A send or connection-request node's `message_templates` is the exception: omitted leaves the step's templates as they are. Its `id` must name an existing body node and its `kind` must match that node's current kind.
agent_idYesID of the agent whose flow holds the node.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare readOnlyHint=false and destructiveHint=false, and the description is consistent (in-place edit, nothing added/dropped). It goes well beyond them by disclosing what is preserved (each prospect keeps its cursor, each node keeps its on_enter enactment) and what is enforced on write (whole-sequence re-validation rejects dangling refs, orphaned nodes, cycles). That is exactly the behavioral context an agent needs before a mutation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and the alternative, then structured into preservation rules, then a cross-tool consistency note. It is long, and the final paragraph on forked-on conditions is somewhat elaborate, but each paragraph carries actionable information rather than filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with two rich parameters, full schema coverage, and no output schema, the description covers purpose, prerequisites, preservation semantics, validation behavior, the alternative tool, and a pointer to get_campaign_flow for reading back the result. Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description still adds meaning: it emphasizes that the node's full new definition must be passed, that `id` selects the node to replace, and that `kind` must match the current kind. This largely reinforces the schema's own notes ('omitted optional slot is dropped, not preserved') rather than introducing new semantics, so it is a modest lift over the structured data.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a precise verb and resource ('Edit one existing node of a campaign's flow in place') and immediately scopes it against the sibling it is not (`not define_sequence`). It even enumerates the concrete edits it covers (message, rule/arms, label, action_description, out-edges), so an agent can distinguish it from define_sequence without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit selection rule: use this when the set of nodes isn't changing; use `define_sequence` to add/remove nodes, change a node's kind, or edit the start node. It also specifies a prerequisite (id must name an existing body node, kind must match current kind) and cross-tool follow-through (update_trigger for prompts). No inference required.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_onboarding_progressUpdate Onboarding ProgressAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
milestoneYesone of 'tool_connected', 'task_executed', 'automation_suggested'
current_taskNodescription of what the user wants to accomplish (set when user first describes their task)

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already indicate this is not read-only and not destructive. The description adds that it returns a dict with success status, but it does not disclose details like idempotency, whether progress is cumulative, or any side effects beyond updating progress. With annotations covering the basic safety profile, this is acceptable but not rich.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is short, front-loaded with the action, and contains no filler. The return description is brief and useful. It could have included more guidance on when not to use the tool, but for its size it is well structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity, full schema coverage, annotations that clarify non-destructive mutation, and a described return value, the description is largely complete. It lacks a little context about how this relates to other onboarding or task-tracking tools, but that is a minor gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% description coverage, including an enum for 'milestone' and a clear description with default for 'current_task'. The description does not need to repeat parameter details, so the baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Update') and the target resource ('the user's onboarding progress'), and it adds a concrete trigger condition ('when a milestone is reached'). It is not a tautology and is distinctive enough among the sibling tools, though it does not explicitly call out any comparable alternative.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly says to call this tool when a milestone is reached, which gives a clear context for use. It does not provide when-not-to-use guidance or name alternatives, so it misses the full 5-point bar.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_prospectUpdate ProspectAInspect

Set priority=True to push this person's queued sends — email and LinkedIn alike — ahead of everyone else's; it's account-wide and durable, carrying across the sequence's later steps for this person. priority=False clears it. One priority flag per prospect, independent of channel: both channels' derived send order reads it.

Most stage transitions — sent, connected, messaged, replied — stamp automatically from send and reply detection; don't set those by hand. The three reply-outcome rungs above replied — interested/meeting_booked (positive) and not_interested (an explicit decline) — normally stamp from the reply classifier too, so set one by hand only to fix a wrong call or record an off-platform outcome: stage='interested' for clear interest, stage='meeting_booked' when they agree to or book a meeting, stage='not_interested' when they've declined. The mark only raises a rung — a decline can't override a live positive (to lower a false meeting_booked/interested, correct that reply with correct_reply_outcome) — and it counts the person as replied, so to drop someone who never engaged use stage='skipped'. Setting a reply-outcome rung also cancels the person's still-pending queued outreach on both channels — automation stops; messages you queue afterwards still send. A later positive reply lifts not_interested back up on its own.

When you set stage='skipped', pass skip_note with the reason in a few words, so a later reader knows why this person was dropped without digging up the chat — a bare skip records no reason.

To reverse a skip and put the person back in the funnel, set stage to the rung they should resume at — normally stage='pending' to re-queue them from the top. That re-enters them into outreach (a still-skipped prospect is held out of every send) and clears the stale skip note; their flow position is preserved, so the sequence resumes from where they were rather than restarting a step already taken.

To store an address you found for a prospect that reached an email step without one (recovering an email_not_found block), pass email. Resolve that prospect by its LinkedIn handle — channel='linkedin' — since a blank-email prospect has no email to match on; the stamped address makes the step send when you retry it. For a blocked prospect, retry_blocked_enactment(email=...) stamps and retries in one call instead.

To undo an accidental hand-set mark (e.g. a prospect wrongly marked meeting_booked), pass clear_manual_mark=True on channel — it removes the mark and re-derives the stage from actual send/reply activity, the only way to lower past a manual mark (which the mark path deliberately floors demotions at). This differs from correct_reply_outcome, which re-judges a specific reply; use clear_manual_mark when the wrong rung came from a hand mark, not a misjudged reply. Pass it on its own (no stage/data/priority/email). Dict with the updated item details. After a skip, skip_outcome says how many queued sends were cancelled and lists any that had already gone out — a skip can't undo those, so tell the user about them. After an email stamp, profile_note is present only when the person's profile was left as it was because another person in the workspace already carries that address — the campaign row still took it; pass the note on to the user.

ParametersJSON Schema
NameRequiredDescriptionDefault
emailNoAn address to store for this prospect (optional). Fills in a prospect that reached an email step without one, so a retried step can send. Lands on the campaign row and on the person's profile, overwriting either's existing address; refused if the address already belongs to another prospect in the account (reconcile the duplicate instead). Leave unset for ordinary stage/priority updates.
stageNoNew stage value (optional) — e.g. 'skipped' to drop the person, 'pending' to reverse a skip and re-queue them.
personYesA handle to the prospect — pass the LinkedIn URL / provider_id / email you already hold; the tool resolves the row within `channel`.
channelYes'linkedin' or 'email' — which funnel the update applies to and which handle on `person` is matched (email against the email column; url/ provider_id against the LinkedIn columns).
agent_idYesID of the agent
priorityNoTrue to prioritize this prospect's queued sends (email and LinkedIn) over everyone else's; False to clear. Account-wide and channel-independent (optional).
skip_noteNoShort free-text reason for a skip, e.g. "off-industry — gold mining, not supplements" (optional). Recorded only when stage='skipped'; ignored otherwise.
data_updatesNoDict to merge into existing data (optional)
linkedin_urlNoA corrected or missing LinkedIn profile URL/slug to bind to THIS prospect in place (optional) — an unverified claim the next send verifies through the profile resolver; any prior verification of the old URL is cleared with it. Refused when the URL already belongs to another of the user's prospects, or when a teammate's prospect verified the same person.
clear_manual_markNoTrue to remove this channel's hand-set reply-outcome mark and re-derive the stage from send/reply activity — the undo for an accidental mark. Pass it alone; combining with stage/data_updates/priority/email is refused.

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With only readOnlyHint=false and destructiveHint=false in annotations, the description carries the behavioral burden and does so thoroughly. It discloses that manual marks only raise a rung, that declines cannot override positive outcomes, that setting a reply outcome cancels pending outreach, that email stamping overwrites existing addresses and can be refused, and that passing linkedin_url clears prior verification.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long, but the complexity of the tool justifies it and every paragraph covers a distinct decision path with no filler. The summary leads with the core purpose, then logically proceeds through stage semantics, skip/reverse behavior, email stamping, and mark undoing, with a separate returns section.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 10 parameters, nested objects, no output schema, and minimal annotations, the description is remarkably complete. It covers return value nuances (skip_outcome, profile_note), refusal conditions, cross-channel effects, and the relationship to sibling tools, so an agent has enough context to select and invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although schema description coverage is 100% (baseline 3), the description substantially enriches parameter semantics: it explains how stage maps per channel, when priority should be set, what skip_note is for, how email resolves blank-email prospects, and how clear_manual_mark differs from correct_reply_outcome. This goes well beyond the individual schema field descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise verb (update), the resource (a single prospect), and the editable dimensions (stage, data, send priority, contact info). It explicitly routes company updates to update_monitored_company and warns against duplicate creation via track_prospects, so it is easily distinguished from siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage guidance is explicit and disambiguated: it names update_monitored_company for companies, retry_blocked_enactment for blocked prospects, correct_reply_outcome for misjudged replies, and clear_manual_mark for accidental hand-set marks. It also states when NOT to use the tool (never re-add a person with changed contact info) and when to set reply outcomes (only to fix or record off-platform).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_segment_groupUpdate Segment GroupAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
modeNo'single' or 'multi'; omit to leave unchanged.
nameNoNew name; omit to leave unchanged.
criteriaNoThe classify scope to track — {agent_ids, senders}, the campaign (agent_tasks) ids and teammate emails, each null = all. A provided object REPLACES the whole stored scope (an axis you leave out becomes all), so pass the current value of any axis you keep; omit the object to leave it unchanged. New people in this scope are tagged automatically and classify reuses it, so change it to re-target what the segment tracks.
group_idYesThe segment group to edit (from list_segment_groups).
descriptionNoNew one-line description; omit to leave unchanged.

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only give readOnlyHint=false and destructiveHint=false; the description adds that edits are partial and, importantly, that editing does NOT re-classify anything and requires a follow-up call. That is genuine side-effect disclosure beyond the annotations, though auth/permission expectations are unstated.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Uses summary/returns sections and front-loads the core action and the critical non-re-classification warning. Slightly re-stated fields but no meaningful waste.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a non-destructive mutation with 5 params and no output schema, it supplies the return dict shape, the partial-update rule, and the required follow-up. Only prerequisite/permission context is absent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the schema already documents omit-to-leave-unchanged and the criteria-replacement semantics. The description's field enumeration largely repeats the schema, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (rename/change) and resource (segment group) plus the exact editable fields (description, mode, classify scope). It also implicitly differentiates itself from create_segment_group, delete_segment_group, and classify_segment_group by naming the classification follow-up step.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Clearly frames this as a partial edit ('Only the fields you pass change') and routes the agent to classify_segment_group(force=True) when re-tagging is needed. It doesn't spell out when to prefer update_segment_tag or other siblings, but the context-conditioned follow-up is explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_segment_tagUpdate Segment TagAInspect
ParametersJSON Schema
NameRequiredDescriptionDefault
nameNoNew name; omit to leave unchanged.
tag_idYesThe tag to edit (from a group's tags list).
descriptionNoNew description; omit to leave unchanged.

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only mark readOnlyHint=false and destructiveHint=false, and the description adds non-obvious behavior: fields are not reset when omitted, and description changes require re-tagging for the classifier to see them. It does not contradict the annotations. It stops short of richer detail like uniqueness or error conditions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The summary is three compact sentences with the main action first, followed by the partial-update rule and the re-tag caveat, plus a small returns section. Every sentence earns its place and there is no padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 3-parameter update tool with annotations covering the safety profile and a returns section describing the response dict {id, name, description, created_by}, the description is complete. It covers what it updates, partial-update semantics, the follow-up re-tag step, and the return shape.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3; the schema already documents tag_id, name, and description. The description adds context that the description is what the classifier reads, but that is workflow information rather than meaningful parameter format semantics beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's function with a specific verb and resource: 'Rename a segment tag or change its description' and emphasizes partial updates. It does not explicitly differentiate itself from sibling tools like delete_segment_tag or update_segment_group, so the purpose is clear but sibling distinction is left to inference.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear contextual guidance for the common post-edit workflow: 're-tag (classify with force=True) after editing it for the change to take effect' and clarifies that only passed fields change. It does not mention when not to use the tool or name alternatives, so it falls just short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_triggerUpdate TriggerAInspect

A send step's message, or a connection request's note, lives in its message_templates, so to change what a step sends, edit the template with update_node; an on_enter prompt on that step carries only extra instructions (tone, whether it may rephrase, what to write when a slot can't be filled), never the message itself.

You MUST call get_skill_guide('trigger_code') before writing code — the qualification rules, sandbox globals, tool surface, and out contract live in the skill. New or changed code runs on the trigger's next firing.

The agent's goal is injected into every turn on this agent, so it steers later runs and replies rather than merely describing them: a trigger edit that changes what the agent does leaves the goal asserting the old behavior. After this call, check the goal — when it describes what you just changed (a step you rewrote, a daily volume or cadence it states, a trigger it says is paused), bring it in line via update_agent(agent_id=..., goal=...) in the same turn. Dict with success, agent_id, trigger_id, next_run_at, and the edited trigger's full dict under trigger, plus a warning when an active trigger sits on a paused agent, since it doesn't fire until the agent is set back to active. Read the agent's other triggers via get_agent_details.

ParametersJSON Schema
NameRequiredDescriptionDefault
codeNoNew trigger code. Runs on the trigger's next firing.
labelNoNew label.
promptNoNew per-run instructions for this trigger. Name any outside resource a run reads (a Google Sheet, doc, file, URL) by the identifier its tool takes (the spreadsheet ID and tab, the URL), not only by its title — a later run doesn't see this chat.
statusNo'active' to resume firing, 'paused' to stop this trigger.
trigger_idYesID of the trigger to edit (from the agent's trigger list).

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=false and destructiveHint=false, so the description carries the rest. It discloses that new/changed code fires on the next run, that an active trigger on a paused agent will not fire (surfaced as a warning), and that this edit can silently desync the agent's goal — real behavioral consequences beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and well-organized into summary/returns blocks, but the middle paragraph on message_templates and on_enter prompts is lengthy and partly tangential to invoking this tool. Every sentence carries information, but the density is high for a 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, yet the description documents the return shape (success, agent_id, trigger_id, next_run_at, trigger, warning) and the paused-agent warning, plus the required skill-guide prerequisite and the goal-sync follow-up. Nothing an agent needs to call this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3, but the description adds meaning the schema doesn't: writing code is gated on get_skill_guide('trigger_code') and status is framed as pause/resume semantics. It does not add format detail for label or trigger_id beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Edit one trigger by id, leaving its other fields untouched') and enumerates the editable surfaces (prompt, code, rename, pause/resume), which cleanly separates it from add_trigger and remove_trigger. An agent can identify the tool without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes the agent: message content lives in message_templates so use update_node, code requires get_skill_guide('trigger_code') first, and goal drift should be repaired via update_agent in the same turn. It names when-not/alternatives rather than leaving them to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_workspaceUpdate WorkspaceAInspect

Use this for structured data. To add an agent-specific operational learning, call append_learning instead.

The app reads inputs: excluded, mailbox, entity_type, discovery_mode, watcher_context, content_calendar, destination; outputs: ideas, drafts, published, digests, insights. Values there must fit the app's shape (a rejected write shows it); for your own data, use another key.

operation='read': Name section and key to get back just that entry; section alone returns that section; neither returns the full workspace ({inputs, outputs}). An entry that was never written comes back as an empty section. The full form re-sends every entry, so name the entry when you know which one you want. operation='update': Requires section, key, and a non-null value. Sets workspace[section][key] = value. operation='append': Requires section, key, and a LIST value. Atomically extends the existing list at workspace[section][key] with value's items (creating it if absent). Use this to accumulate into a list — a digest entry, new calendar topics, freshly-created draft records — without reading, concatenating, and rewriting the whole array yourself (which races other writers). Errors if the current value isn't a list (use 'update' to replace it). operation='delete': Requires section and key. ALWAYS confirm with the user before calling — this is destructive. Shape depends on the operation. On read, {'success': True, 'agent_id': ..., 'workspace': {'inputs': {...}, 'outputs': {...}}}, with 'workspace' holding only the section — or only the one section/key entry — you named. On update or delete, {'success': True, 'agent_id': ..., 'workspace_keys': {'inputs': [...], 'outputs': [...]}}

ParametersJSON Schema
NameRequiredDescriptionDefault
keyNoThe key to set, extend, or delete (required for update/append/delete; optional narrowing for read, where it also needs section)
valueNoValue to store (required for update; must not be None — use operation='delete' to remove a key). Name any outside resource a run reads (a Google Sheet, doc, file, URL) by the identifier its tool takes (the spreadsheet ID and tab, the URL), not only by its title — a later run doesn't see this chat. For append, a list of items to add to the existing list.
sectionNoWhich workspace section — 'inputs' or 'outputs' (required for update/append/delete; optional narrowing for read)
agent_idYesID of the agent
operationYes'read', 'update', 'append', or 'delete'

TDQS

A3.9/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description is unusually rich behaviorally — atomic append vs read-concat-rewrite race, append failing on non-list values, empty sections for never-written entries, full-form read resending every entry, rejected writes showing app shape. However, it calls delete 'destructive' and demands user confirmation, which directly contradicts the destructiveHint=false annotation; that inconsistency is the reason this is not scored higher.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Content is front-loaded on the two sections and mostly earns its place, but the description is bloated: XML-style <summary>/<returns> wrappers inside a description field, and operation semantics that duplicate the schema. A tighter form would communicate the same routing and behavior in fewer words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite no output schema, the description embeds a <returns> block explaining the response shape per operation (workspace vs workspace_keys), plus per-operation preconditions and failure modes. For a 4-mode mutation tool, an agent has everything needed to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description restates the per-operation parameter requirements (section/key/value for update, list value for append) that the schema already documents, adding little new parameter meaning beyond the schema's own text.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb(s)+resource (read/update/append/delete entries in an agent's workspace) and immediately explains the two sections and their rendering. It explicitly distinguishes itself from `record_search_results` for bulk entity sets and from `append_learning` for operational learnings, so an agent can route without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use and when-not-to-use guidance: use record_search_results for any bulk people/companies set, append_learning for operational learnings, and this tool for structured data. Per-operation requirements (append only for lists, delete requires confirmation) further pin down usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool update
    • Addedget_mcp_change_calls
  2. 8 tool updates
    • Changeddefine_sequence1 field changed
      • changedInput schema / properties / sequence / properties / nodes / items / oneOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
        -    "properties": {
        -      "accepted": {
        -        "type": "string"
        -      },
        -      "already_connected": {
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "connection_request",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "timeout": {
        -        "anyOf": [
        -          {
        -            "additionalProperties": false,
        -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        -            "properties": {
        -              "delay": {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              "to": {
        -                "type": "string"
        -              }
        -            },
        -            "required": [
        -              "delay",
        -              "to"
        -            ],
        -            "title": "TimedAdvance",
        -            "type": "object"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "accepted",
        -      "already_connected"
        -    ],
        -    "title": "ConnectionRequestNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).\n\n`message_templates` is input-only: excluded from every dump, so it never lands in the stored\nsequence — the step's templates live in their own rows and are merged back in on read. A step\nwith two is running an A/B test.",
        -    "properties": {
        -      "action": {
        -        "enum": [
        -          "message",
        -          "inmail",
        -          "email"
        -        ],
        -        "type": "string"
        -      },
        -      "channel": {
        -        "enum": [
        -          "linkedin",
        -          "email"
        -        ],
        -        "type": "string"
        -      },
        -      "follow_up": {
        -        "anyOf": [
        -          {
        -            "additionalProperties": false,
        -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        -            "properties": {
        -              "delay": {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              "to": {
        -                "type": "string"
        -              }
        -            },
        -            "required": [
        -              "delay",
        -              "to"
        -            ],
        -            "title": "TimedAdvance",
        -            "type": "object"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "send",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "message_templates": {
        -        "anyOf": [
        -          {
        -            "items": {
        -              "type": "string"
        -            },
        -            "maxItems": 2,
        -            "type": "array"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "replied": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "channel",
        -      "action",
        -      "replied"
        -    ],
        -    "title": "SendNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
        -    "properties": {
        -      "action": {
        -        "enum": [
        -          "follow",
        -          "withdraw",
        -          "resolve",
        -          "comment",
        -          "reaction"
        -        ],
        -        "type": "string"
        -      },
        -      "channel": {
        -        "const": "linkedin",
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "action",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "notify": {
        -        "default": false,
        -        "type": "boolean"
        -      },
        -      "then": {
        -        "additionalProperties": false,
        -        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
        -        "properties": {
        -          "after": {
        -            "anyOf": [
        -              {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              {
        -                "type": "null"
        -              }
        -            ],
        -            "default": null
        -          },
        -          "to": {
        -            "type": "string"
        -          }
        -        },
        -        "required": [
        -          "to"
        -        ],
        -        "title": "Advance",
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "channel",
        -      "action",
        -      "then"
        -    ],
        -    "title": "SilentActionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
        -    "properties": {
        -      "action_description": {
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "manual",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "then": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "action_description",
        -      "then"
        -    ],
        -    "title": "ManualActionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a terminal node's hook — not a field here. Unlike a manual node\nit boots an agent run (rather than waiting on a human) and unlike a terminal hook it\nadvances; structurally it is a decision with one implicit 'always' arm — the run does the\nside-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
        -    "properties": {
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "automation",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "then": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "then"
        -    ],
        -    "title": "AutomationNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
        -    "properties": {
        -      "arms": {
        -        "items": {
        -          "additionalProperties": false,
        -          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
        -          "properties": {
        -            "case": {
        -              "minLength": 1,
        -              "type": "string"
        -            },
        -            "to": {
        -              "type": "string"
        -            }
        -          },
        -          "required": [
        -            "case",
        -            "to"
        -          ],
        -          "title": "Arm",
        -          "type": "object"
        -        },
        -        "minItems": 1,
        -        "type": "array"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "decision",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "rule": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "rule",
        -      "arms"
        -    ],
        -    "title": "DecisionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
        -    "properties": {
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "terminal",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind"
        -    ],
        -    "title": "TerminalNode",
        -    "type": "object"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).\n\n`message_templates` is input-only, as on a send node, and holds the request's note (at most\n300 characters): `''` is a version sent without one, so `['', note]` A/B tests a note\nagainst none.",
        +    "properties": {
        +      "accepted": {
        +        "type": "string"
        +      },
        +      "already_connected": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "connection_request",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "message_templates": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "maxLength": 300,
        +              "type": "string"
        +            },
        +            "maxItems": 2,
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "timeout": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        +            "properties": {
        +              "delay": {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              "to": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "delay",
        +              "to"
        +            ],
        +            "title": "TimedAdvance",
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "accepted",
        +      "already_connected"
        +    ],
        +    "title": "ConnectionRequestNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).\n\n`message_templates` is input-only: excluded from every dump, so it never lands in the stored\nsequence — the step's templates live in their own rows and are merged back in on read. A step\nwith two is running an A/B test.",
        +    "properties": {
        +      "action": {
        +        "enum": [
        +          "message",
        +          "inmail",
        +          "email"
        +        ],
        +        "type": "string"
        +      },
        +      "channel": {
        +        "enum": [
        +          "linkedin",
        +          "email"
        +        ],
        +        "type": "string"
        +      },
        +      "follow_up": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        +            "properties": {
        +              "delay": {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              "to": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "delay",
        +              "to"
        +            ],
        +            "title": "TimedAdvance",
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "send",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "message_templates": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "string"
        +            },
        +            "maxItems": 2,
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "replied": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "channel",
        +      "action",
        +      "replied"
        +    ],
        +    "title": "SendNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
        +    "properties": {
        +      "action": {
        +        "enum": [
        +          "follow",
        +          "withdraw",
        +          "resolve",
        +          "comment",
        +          "reaction"
        +        ],
        +        "type": "string"
        +      },
        +      "channel": {
        +        "const": "linkedin",
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "action",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "notify": {
        +        "default": false,
        +        "type": "boolean"
        +      },
        +      "then": {
        +        "additionalProperties": false,
        +        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
        +        "properties": {
        +          "after": {
        +            "anyOf": [
        +              {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "default": null
        +          },
        +          "to": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "to"
        +        ],
        +        "title": "Advance",
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "channel",
        +      "action",
        +      "then"
        +    ],
        +    "title": "SilentActionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
        +    "properties": {
        +      "action_description": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "manual",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "then": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "action_description",
        +      "then"
        +    ],
        +    "title": "ManualActionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a terminal node's hook — not a field here. Unlike a manual node\nit boots an agent run (rather than waiting on a human) and unlike a terminal hook it\nadvances; structurally it is a decision with one implicit 'always' arm — the run does the\nside-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
        +    "properties": {
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "automation",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "then": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "then"
        +    ],
        +    "title": "AutomationNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
        +    "properties": {
        +      "arms": {
        +        "items": {
        +          "additionalProperties": false,
        +          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
        +          "properties": {
        +            "case": {
        +              "minLength": 1,
        +              "type": "string"
        +            },
        +            "to": {
        +              "type": "string"
        +            }
        +          },
        +          "required": [
        +            "case",
        +            "to"
        +          ],
        +          "title": "Arm",
        +          "type": "object"
        +        },
        +        "minItems": 1,
        +        "type": "array"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "decision",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "rule": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "rule",
        +      "arms"
        +    ],
        +    "title": "DecisionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
        +    "properties": {
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "terminal",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind"
        +    ],
        +    "title": "TerminalNode",
        +    "type": "object"
        +  }
        +]
    • Changedget_message_experiments3 fields changed
      • changedInput schema / properties / node_id / description
        Previous value: -"A send step's node id, to scope to that step. Needs `agent_id`."New value: +"A send or connection-request step's node id, to scope to that step. Needs\n`agent_id`."
      • changedInput schema / properties / outcome / anyOf
        Previous value: -[
        -  {
        -    "enum": [
        -      "replied",
        -      "interested",
        -      "meeting"
        -    ],
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "enum": [
        +      "accepted",
        +      "replied",
        +      "interested",
        +      "meeting"
        +    ],
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / outcome / description
        Previous value: -"Only messages whose prospect reached this outcome (meeting counts as interested\nand replied; interested counts as replied). None = every sent message."New value: +"Only messages whose prospect reached this outcome (meeting counts as interested\nand replied; interested counts as replied; on a connection request, replied counts as\naccepted). None = every sent message."
    • Changedquery_search_results2 fields changed
      • addedInput schema / properties / include_rejected
        Added value: +{
        +  "default": false,
        +  "description": "Also return (and count) the rows marked rejected. Default\nFalse.",
        +  "type": "boolean"
        +}
      • addedInput schema / properties / verdict
        Added value: +{
        +  "anyOf": [
        +    {
        +      "description": "The agent's curation call on an `agent_search_results` row. NULL (no verdict) means\nunreviewed; the default task views hide only `REJECTED` rows.",
        +      "enum": [
        +        "qualified",
        +        "rejected"
        +      ],
        +      "title": "SearchResultVerdict",
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Only the rows with this verdict, 'qualified' or 'rejected' ('rejected'\nreads the rows the user's default view filters out). Omit for every verdict."
        +}
    • Changedquery_task_people2 fields changed
      • changedInput schema / properties / filters / description
        Previous value: -"Clauses that must all match. For \"degree\", \"outreach_stage\", \"list\" or\n\"source\", use {\"column_key\": <dimension>, \"operator\": \"is_any_of\", \"values\": [keys]}\nwith keys as group_counts returns them ('' matches no value). For \"segment\", use the\nsame shape with the segment keys above.\nFor \"name\", \"title\", \"headline\", \"company\" or \"location\", use {\"column_key\":\n<column>, \"operator\": \"contains\", \"text\": <substring>}."New value: +"Clauses that must all match. For \"degree\", \"outreach_stage\", \"list\" or\n\"source\", use {\"column_key\": <dimension>, \"operator\": \"is_any_of\", \"values\": [keys]}\nwith keys as group_counts returns them ('' matches no value). For \"segment\", use the\nsame shape with the segment keys above. For \"verdict\", use the same shape with\n'rejected' (the people the user's default view filters out) and/or 'qualified'; it\nmatches each person's `verdict`, or with group_by=\"list\" their verdict on each list,\nand 'rejected' brings the rejected people in without `include_rejected`. For \"name\",\n\"title\", \"headline\", \"company\" or \"location\", use {\"column_key\": <column>,\n\"operator\": \"contains\", \"text\": <substring>}."
      • addedInput schema / properties / include_rejected
        Added value: +{
        +  "default": false,
        +  "description": "Also return (and count) the people left out as rejected. Default False.",
        +  "type": "boolean"
        +}
    • Changedrecord_search_results4 fields changed
      • changedInput schema / properties / list_name / description
        Previous value: -"short kebab slug naming the bucket these rows belong to\n(e.g. 'oil-gas-operators', 'fintech', 'companies'). Required.\nDistinct slugs render as separate sub-pills in the Output tab; reuse\nthe same slug across calls to accumulate into one list. For\nuncategorized runs, pick a single descriptive slug (e.g. the\nentity_type plural — 'companies' / 'people') and use it consistently."New value: +"short kebab slug naming the bucket these rows belong to\n(e.g. 'oil-gas-operators', 'fintech', 'companies'). Required.\nDistinct slugs are separate lists on the agent's People and Companies\ntabs; reuse the same slug across calls to accumulate into one list. For\nuncategorized runs, pick a single descriptive slug (e.g. the\nentity_type plural — 'companies' / 'people') and use it consistently."
      • changedInput schema / properties / results / description
        Previous value: -"The people/company rows to upsert; each SearchResult carries its\nidentifier, display_name, source, and any signals/columns to store."New value: +"The people/company rows to upsert; each SearchResult carries its\nidentifier, display_name, source, any signals/columns to store, and\noptionally its verdict and verdict_reason."
      • addedInput schema / properties / results / items / properties / verdict
        Added value: +{
        +  "anyOf": [
        +    {
        +      "description": "The agent's curation call on an `agent_search_results` row. NULL (no verdict) means\nunreviewed; the default task views hide only `REJECTED` rows.",
        +      "enum": [
        +        "qualified",
        +        "rejected"
        +      ],
        +      "title": "SearchResultVerdict",
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Your judgment of this row after screening it: 'qualified' keeps it on the list; 'rejected' keeps it saved but out of the list the user sees by default, and they see it, with `verdict_reason`, when they filter the table on Verdict. Omit it to leave the row's current verdict as it is."
        +}
      • addedInput schema / properties / results / items / properties / verdict_reason
        Added value: +{
        +  "default": "",
        +  "description": "Why, in one short sentence the user reads in the row's Verdict column (e.g. 'HQ in Toronto — outside the US location filter'). Required with 'rejected'; set it only with a verdict.",
        +  "type": "string"
        +}
    • Changedsetup_email_sequence1 field changed
      • changedInput schema / properties / node_id / description
        Previous value: -"Optional. The id of the sequence-DAG node this batch enacts\n(a node in the agent's `sequence`, authored via `define_sequence`).\nStamped on every queued row so the Campaign Flow view can place\neach prospect and count per node. Pass it whenever the agent has a\nsequence; omit for flowless/ad-hoc sends. Rejected with ModelRetry\nif it isn't a node in the agent's sequence."New value: +"Optional. The id of the sequence-DAG node this batch enacts\n(a node in the agent's `sequence`, authored via `define_sequence`).\nStamped on every queued row so the Campaign Flow view can place\neach prospect and count per node. Pass it whenever the agent has a\nsequence. Rejected with ModelRetry if it isn't a node in the agent's\nsequence."
    • Changedsetup_linkedin_sequence1 field changed
      • changedInput schema / properties / node_id / description
        Previous value: -"Optional. The id of the sequence-DAG node this batch enacts\n(a node in the agent's `sequence`, authored via `define_sequence`).\nStamped on every queued row so the Campaign Flow view can place\neach prospect and count per node. Pass it whenever the agent has a\nsequence; omit for flowless/ad-hoc sends. Rejected with ModelRetry\nif it isn't a node in the agent's sequence."New value: +"Optional. The id of the sequence-DAG node this batch enacts\n(a node in the agent's `sequence`, authored via `define_sequence`).\nStamped on every queued row so the Campaign Flow view can place\neach prospect and count per node. Pass it whenever the agent has a\nsequence. Rejected with ModelRetry if it isn't a node in the agent's\nsequence."
    • Changedupdate_node2 fields changed
      • changedInput schema / properties / node / description
        Previous value: -"The node's complete new definition — an omitted optional slot (a `follow_up`, a\n`timeout`) is dropped, not preserved, so carry forward every field the node keeps.\nA send node's `message_templates` is the exception: omitted leaves the step's\ntemplates as they are.\nIts `id` must name an existing body node and its `kind` must match that node's\ncurrent kind."New value: +"The node's complete new definition — an omitted optional slot (a `follow_up`, a\n`timeout`) is dropped, not preserved, so carry forward every field the node keeps.\nA send or connection-request node's `message_templates` is the exception: omitted\nleaves the step's templates as they are.\nIts `id` must name an existing body node and its `kind` must match that node's\ncurrent kind."
      • changedInput schema / properties / node / oneOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
        -    "properties": {
        -      "accepted": {
        -        "type": "string"
        -      },
        -      "already_connected": {
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "connection_request",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "timeout": {
        -        "anyOf": [
        -          {
        -            "additionalProperties": false,
        -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        -            "properties": {
        -              "delay": {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              "to": {
        -                "type": "string"
        -              }
        -            },
        -            "required": [
        -              "delay",
        -              "to"
        -            ],
        -            "title": "TimedAdvance",
        -            "type": "object"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "accepted",
        -      "already_connected"
        -    ],
        -    "title": "ConnectionRequestNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).\n\n`message_templates` is input-only: excluded from every dump, so it never lands in the stored\nsequence — the step's templates live in their own rows and are merged back in on read. A step\nwith two is running an A/B test.",
        -    "properties": {
        -      "action": {
        -        "enum": [
        -          "message",
        -          "inmail",
        -          "email"
        -        ],
        -        "type": "string"
        -      },
        -      "channel": {
        -        "enum": [
        -          "linkedin",
        -          "email"
        -        ],
        -        "type": "string"
        -      },
        -      "follow_up": {
        -        "anyOf": [
        -          {
        -            "additionalProperties": false,
        -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        -            "properties": {
        -              "delay": {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              "to": {
        -                "type": "string"
        -              }
        -            },
        -            "required": [
        -              "delay",
        -              "to"
        -            ],
        -            "title": "TimedAdvance",
        -            "type": "object"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "send",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "message_templates": {
        -        "anyOf": [
        -          {
        -            "items": {
        -              "type": "string"
        -            },
        -            "maxItems": 2,
        -            "type": "array"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "replied": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "channel",
        -      "action",
        -      "replied"
        -    ],
        -    "title": "SendNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
        -    "properties": {
        -      "action": {
        -        "enum": [
        -          "follow",
        -          "withdraw",
        -          "resolve",
        -          "comment",
        -          "reaction"
        -        ],
        -        "type": "string"
        -      },
        -      "channel": {
        -        "const": "linkedin",
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "action",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "notify": {
        -        "default": false,
        -        "type": "boolean"
        -      },
        -      "then": {
        -        "additionalProperties": false,
        -        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
        -        "properties": {
        -          "after": {
        -            "anyOf": [
        -              {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              {
        -                "type": "null"
        -              }
        -            ],
        -            "default": null
        -          },
        -          "to": {
        -            "type": "string"
        -          }
        -        },
        -        "required": [
        -          "to"
        -        ],
        -        "title": "Advance",
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "channel",
        -      "action",
        -      "then"
        -    ],
        -    "title": "SilentActionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
        -    "properties": {
        -      "action_description": {
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "manual",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "then": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "action_description",
        -      "then"
        -    ],
        -    "title": "ManualActionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a terminal node's hook — not a field here. Unlike a manual node\nit boots an agent run (rather than waiting on a human) and unlike a terminal hook it\nadvances; structurally it is a decision with one implicit 'always' arm — the run does the\nside-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
        -    "properties": {
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "automation",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "then": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "then"
        -    ],
        -    "title": "AutomationNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
        -    "properties": {
        -      "arms": {
        -        "items": {
        -          "additionalProperties": false,
        -          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
        -          "properties": {
        -            "case": {
        -              "minLength": 1,
        -              "type": "string"
        -            },
        -            "to": {
        -              "type": "string"
        -            }
        -          },
        -          "required": [
        -            "case",
        -            "to"
        -          ],
        -          "title": "Arm",
        -          "type": "object"
        -        },
        -        "minItems": 1,
        -        "type": "array"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "decision",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "rule": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "rule",
        -      "arms"
        -    ],
        -    "title": "DecisionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
        -    "properties": {
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "terminal",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind"
        -    ],
        -    "title": "TerminalNode",
        -    "type": "object"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).\n\n`message_templates` is input-only, as on a send node, and holds the request's note (at most\n300 characters): `''` is a version sent without one, so `['', note]` A/B tests a note\nagainst none.",
        +    "properties": {
        +      "accepted": {
        +        "type": "string"
        +      },
        +      "already_connected": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "connection_request",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "message_templates": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "maxLength": 300,
        +              "type": "string"
        +            },
        +            "maxItems": 2,
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "timeout": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        +            "properties": {
        +              "delay": {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              "to": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "delay",
        +              "to"
        +            ],
        +            "title": "TimedAdvance",
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "accepted",
        +      "already_connected"
        +    ],
        +    "title": "ConnectionRequestNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).\n\n`message_templates` is input-only: excluded from every dump, so it never lands in the stored\nsequence — the step's templates live in their own rows and are merged back in on read. A step\nwith two is running an A/B test.",
        +    "properties": {
        +      "action": {
        +        "enum": [
        +          "message",
        +          "inmail",
        +          "email"
        +        ],
        +        "type": "string"
        +      },
        +      "channel": {
        +        "enum": [
        +          "linkedin",
        +          "email"
        +        ],
        +        "type": "string"
        +      },
        +      "follow_up": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        +            "properties": {
        +              "delay": {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              "to": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "delay",
        +              "to"
        +            ],
        +            "title": "TimedAdvance",
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "send",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "message_templates": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "string"
        +            },
        +            "maxItems": 2,
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "replied": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "channel",
        +      "action",
        +      "replied"
        +    ],
        +    "title": "SendNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
        +    "properties": {
        +      "action": {
        +        "enum": [
        +          "follow",
        +          "withdraw",
        +          "resolve",
        +          "comment",
        +          "reaction"
        +        ],
        +        "type": "string"
        +      },
        +      "channel": {
        +        "const": "linkedin",
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "action",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "notify": {
        +        "default": false,
        +        "type": "boolean"
        +      },
        +      "then": {
        +        "additionalProperties": false,
        +        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
        +        "properties": {
        +          "after": {
        +            "anyOf": [
        +              {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "default": null
        +          },
        +          "to": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "to"
        +        ],
        +        "title": "Advance",
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "channel",
        +      "action",
        +      "then"
        +    ],
        +    "title": "SilentActionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
        +    "properties": {
        +      "action_description": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "manual",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "then": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "action_description",
        +      "then"
        +    ],
        +    "title": "ManualActionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a terminal node's hook — not a field here. Unlike a manual node\nit boots an agent run (rather than waiting on a human) and unlike a terminal hook it\nadvances; structurally it is a decision with one implicit 'always' arm — the run does the\nside-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
        +    "properties": {
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "automation",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "then": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "then"
        +    ],
        +    "title": "AutomationNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
        +    "properties": {
        +      "arms": {
        +        "items": {
        +          "additionalProperties": false,
        +          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
        +          "properties": {
        +            "case": {
        +              "minLength": 1,
        +              "type": "string"
        +            },
        +            "to": {
        +              "type": "string"
        +            }
        +          },
        +          "required": [
        +            "case",
        +            "to"
        +          ],
        +          "title": "Arm",
        +          "type": "object"
        +        },
        +        "minItems": 1,
        +        "type": "array"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "decision",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "rule": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "rule",
        +      "arms"
        +    ],
        +    "title": "DecisionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
        +    "properties": {
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "terminal",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind"
        +    ],
        +    "title": "TerminalNode",
        +    "type": "object"
        +  }
        +]
  3. 5 tool updates
    • Changedcorrect_message_classification1 field changed
      • changedInput schema / properties / source / description
        Previous value: -"Which table the message is in — 'linkedin_data' (a LinkedIn DM), 'email_data'\n(a sent email), or 'linkedin_queue' (a connection-request note). This is a message's\n`source` field from query_tagged_messages."New value: +"Which table the message is in — 'linkedin_data' (a LinkedIn DM), 'email_data'\n(a sent email), or 'linkedin_queue' (a connection-request note). This is a message's\n`source` field from list_sent_messages or query_tagged_messages."
    • Addedget_segment_funnel
    • Addedlist_sent_messages
    • Changedquery_task_people4 fields changed
      • changedInput schema / properties / filters / description
        Previous value: -"Clauses that must all match. For \"degree\", \"outreach_stage\", \"list\" or\n\"source\", use {\"column_key\": <dimension>, \"operator\": \"is_any_of\", \"values\": [keys]}\nwith keys as group_counts returns them ('' matches no value). For \"name\", \"title\",\n\"headline\", \"company\" or \"location\", use {\"column_key\": <column>, \"operator\":\n\"contains\", \"text\": <substring>}."New value: +"Clauses that must all match. For \"degree\", \"outreach_stage\", \"list\" or\n\"source\", use {\"column_key\": <dimension>, \"operator\": \"is_any_of\", \"values\": [keys]}\nwith keys as group_counts returns them ('' matches no value). For \"segment\", use the\nsame shape with the segment keys above.\nFor \"name\", \"title\", \"headline\", \"company\" or \"location\", use {\"column_key\":\n<column>, \"operator\": \"contains\", \"text\": <substring>}."
      • changedInput schema / properties / group_by / anyOf
        Previous value: -[
        -  {
        -    "enum": [
        -      "title",
        -      "company",
        -      "location",
        -      "outreach_stage",
        -      "list",
        -      "source",
        -      "degree",
        -      "connector"
        -    ],
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "enum": [
        +      "title",
        +      "company",
        +      "location",
        +      "outreach_stage",
        +      "list",
        +      "source",
        +      "degree",
        +      "connector",
        +      "segment"
        +    ],
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / group_by / description
        Previous value: -"The dimension to count or to pick a bucket from. \"degree\" keys are '1', '2',\n'3', 'out_of_network'; \"connector\" keys are a connector's provider_id (else their\nidentifier or name) and carry the connector's name as `label`; \"outreach_stage\"\nkeys are funnel buckets; \"list\" and \"source\" keys are list names and discovery tools; \"title\", \"company\" and \"location\"\nkey on the person's own value. A '' key is the group with no value. A person with\nseveral lists, sources or connectors counts in each."New value: +"The dimension to count or to pick a bucket from. \"degree\" keys are '1', '2',\n'3', 'out_of_network'; \"connector\" keys are a connector's provider_id (else their\nidentifier or name) and carry the connector's name as `label`; \"outreach_stage\"\nkeys are funnel buckets; \"list\" and \"source\" keys are list names and discovery tools;\n\"segment\" keys are tag names in `segment_group_id`'s group, plus 'No match' for people\nthe classifier couldn't place; \"title\", \"company\" and \"location\" key on the person's\nown value. A '' key is the group with no value (for \"segment\", people the group hasn't\nclassified). A person with several lists, sources, connectors or tags counts in each."
      • addedInput schema / properties / segment_group_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "The segment group (from list_segment_groups or get_segment_funnel) a\n\"segment\" filter or group_by reads. With it, each row also carries `segment`, its tag\nnames in that group."
        +}
    • Changedupdate_segment_group1 field changed
      • changedInput schema / properties / criteria / description
        Previous value: -"The classify scope to track — {agent_ids, senders}, the campaign (agent_tasks)\nids and teammate emails, each null = all. A provided object REPLACES the whole stored\nscope (an axis you leave out becomes all), so pass the current value of any axis you\nkeep; omit the object to leave it unchanged. classify reuses this scope, so change it\nto re-target what the segment tracks."New value: +"The classify scope to track — {agent_ids, senders}, the campaign (agent_tasks)\nids and teammate emails, each null = all. A provided object REPLACES the whole stored\nscope (an axis you leave out becomes all), so pass the current value of any axis you\nkeep; omit the object to leave it unchanged. New people in this scope are tagged\nautomatically and classify reuses it, so change it to re-target what the segment\ntracks."
  4. 12 tool updates
    • Changedadd_trigger1 field changed
      • changedInput schema / properties / trigger / description
        Previous value: -"The trigger to add"New value: +"The trigger to add. In its prompt, name any outside resource a run reads (a Google Sheet, doc, file, URL) by the identifier its tool takes (the spreadsheet ID and tab, the URL), not only by its title — a later run doesn't see this chat."
    • Changedcreate_agent3 fields changed
      • changedInput schema / properties / goal / description
        Previous value: -"Complete instructions for what the agent should accomplish. Write as if instructing another assistant."New value: +"Complete instructions for what the agent should accomplish. Write as if instructing another assistant. Name any outside resource a run reads (a Google Sheet, doc, file, URL) by the identifier its tool takes (the spreadsheet ID and tab, the URL), not only by its title — a later run doesn't see this chat."
      • changedInput schema / properties / triggers / description
        Previous value: -"List of triggers that determine when the agent runs. An agent can have multiple triggers of different types (e.g. one schedule plus one event handler) — they fire independently. At least one trigger is required."New value: +"List of triggers that determine when the agent runs. An agent can have multiple triggers of different types (e.g. one schedule plus one event handler) — they fire independently. At least one trigger is required. In each trigger's prompt, name any outside resource a run reads (a Google Sheet, doc, file, URL) by the identifier its tool takes (the spreadsheet ID and tab, the URL), not only by its title — a later run doesn't see this chat."
      • changedInput schema / properties / workspace / description
        Previous value: -"Optional `{'inputs': {...}, 'outputs': {...}}` dict that seeds the agent's workspace."New value: +"Optional `{'inputs': {...}, 'outputs': {...}}` dict that seeds the agent's workspace. Name any outside resource a run reads (a Google Sheet, doc, file, URL) by the identifier its tool takes (the spreadsheet ID and tab, the URL), not only by its title — a later run doesn't see this chat."
    • Changedcreate_message_tag_group1 field changed
      • changedInput schema / properties / criteria / anyOf
        Previous value: -[
        -  {
        -    "description": "The persisted classify scope a question tracks. Each axis null = all (every channel /\nevery campaign / every conversation position / every teammate). Omit the whole object to leave\ncriteria unset (create) or unchanged (edit).",
        -    "properties": {
        -      "agent_ids": {
        -        "anyOf": [
        -          {
        -            "items": {
        -              "type": "integer"
        -            },
        -            "type": "array"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "channel": {
        -        "anyOf": [
        -          {
        -            "description": "The outreach channel a query is bounded to; None means both.",
        -            "enum": [
        -              "linkedin",
        -              "email"
        -            ],
        -            "title": "Channel",
        -            "type": "string"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "positions": {
        -        "anyOf": [
        -          {
        -            "items": {
        -              "description": "A send's spot in its conversation; a positions filter is a list of these, None for all.\nThe conversation's first outbound send is 'first', a later one sent before the prospect's\nfirst reply is 'follow_up', and any send after that reply is 'reply'. Derived, never stored\nper message.",
        -              "enum": [
        -                "first",
        -                "follow_up",
        -                "reply"
        -              ],
        -              "title": "Position",
        -              "type": "string"
        -            },
        -            "minItems": 1,
        -            "type": "array"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "senders": {
        -        "anyOf": [
        -          {
        -            "items": {
        -              "type": "string"
        -            },
        -            "type": "array"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      }
        -    },
        -    "title": "CriteriaInput",
        -    "type": "object"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "description": "The persisted classify scope a question tracks. Each axis null = all (every channel /\nevery campaign / every conversation position / every teammate). Omit the whole object to leave\ncriteria unset (create) or unchanged (edit).",
        +    "properties": {
        +      "agent_ids": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "integer"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "channel": {
        +        "anyOf": [
        +          {
        +            "description": "The outreach channel a query is bounded to; None means both.",
        +            "enum": [
        +              "linkedin",
        +              "email"
        +            ],
        +            "title": "Channel",
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "positions": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "description": "A send's spot in its conversation; a positions filter is a list of these, None for all.\nThe conversation's first outbound send is 'first', a later one sent before the prospect's\nfirst reply is 'follow_up', and any send after that reply is 'reply'. Derived, never stored\nper message.",
        +              "enum": [
        +                "first",
        +                "follow_up",
        +                "reply"
        +              ],
        +              "title": "Position",
        +              "type": "string"
        +            },
        +            "minItems": 1,
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "senders": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      }
        +    },
        +    "title": "Criteria",
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
    • Changedcreate_segment_group1 field changed
      • changedInput schema / properties / criteria / anyOf
        Previous value: -[
        -  {
        -    "description": "The persisted classify scope a segment tracks — campaigns and teammates (a person has no\nintrinsic channel, so channel is a view filter, never a classify criterion). Each axis null =\nall (every campaign / every teammate). Omit the whole object to leave criteria unset (create)\nor unchanged (edit).",
        -    "properties": {
        -      "agent_ids": {
        -        "anyOf": [
        -          {
        -            "items": {
        -              "type": "integer"
        -            },
        -            "type": "array"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "senders": {
        -        "anyOf": [
        -          {
        -            "items": {
        -              "type": "string"
        -            },
        -            "type": "array"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      }
        -    },
        -    "title": "CriteriaInput",
        -    "type": "object"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "description": "The persisted classify scope a segment tracks — campaigns and teammates (a person has no\nintrinsic channel, so channel is a view filter, never a classify criterion). Each axis null =\nall (every campaign / every teammate). Omit the whole object to leave criteria unset (create)\nor unchanged (edit).",
        +    "properties": {
        +      "agent_ids": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "integer"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "senders": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      }
        +    },
        +    "title": "Criteria",
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
    • Removedget_message_tag_cross_tab
    • Addedget_tag_cross_tab
    • Changedquery_task_people1 field changed
      • addedInput schema / properties / include_criteria
        Added value: +{
        +  "default": false,
        +  "description": "Also return each row's `criteria`. Default False.",
        +  "type": "boolean"
        +}
    • Changedupdate_agent1 field changed
      • changedInput schema / properties / goal / description
        Previous value: -"New goal/instructions"New value: +"New goal/instructions. Name any outside resource a run reads (a Google Sheet, doc, file, URL) by the identifier its tool takes (the spreadsheet ID and tab, the URL), not only by its title — a later run doesn't see this chat."
    • Changedupdate_message_tag_group1 field changed
      • changedInput schema / properties / criteria / anyOf
        Previous value: -[
        -  {
        -    "description": "The persisted classify scope a question tracks. Each axis null = all (every channel /\nevery campaign / every conversation position / every teammate). Omit the whole object to leave\ncriteria unset (create) or unchanged (edit).",
        -    "properties": {
        -      "agent_ids": {
        -        "anyOf": [
        -          {
        -            "items": {
        -              "type": "integer"
        -            },
        -            "type": "array"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "channel": {
        -        "anyOf": [
        -          {
        -            "description": "The outreach channel a query is bounded to; None means both.",
        -            "enum": [
        -              "linkedin",
        -              "email"
        -            ],
        -            "title": "Channel",
        -            "type": "string"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "positions": {
        -        "anyOf": [
        -          {
        -            "items": {
        -              "description": "A send's spot in its conversation; a positions filter is a list of these, None for all.\nThe conversation's first outbound send is 'first', a later one sent before the prospect's\nfirst reply is 'follow_up', and any send after that reply is 'reply'. Derived, never stored\nper message.",
        -              "enum": [
        -                "first",
        -                "follow_up",
        -                "reply"
        -              ],
        -              "title": "Position",
        -              "type": "string"
        -            },
        -            "minItems": 1,
        -            "type": "array"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "senders": {
        -        "anyOf": [
        -          {
        -            "items": {
        -              "type": "string"
        -            },
        -            "type": "array"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      }
        -    },
        -    "title": "CriteriaInput",
        -    "type": "object"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "description": "The persisted classify scope a question tracks. Each axis null = all (every channel /\nevery campaign / every conversation position / every teammate). Omit the whole object to leave\ncriteria unset (create) or unchanged (edit).",
        +    "properties": {
        +      "agent_ids": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "integer"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "channel": {
        +        "anyOf": [
        +          {
        +            "description": "The outreach channel a query is bounded to; None means both.",
        +            "enum": [
        +              "linkedin",
        +              "email"
        +            ],
        +            "title": "Channel",
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "positions": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "description": "A send's spot in its conversation; a positions filter is a list of these, None for all.\nThe conversation's first outbound send is 'first', a later one sent before the prospect's\nfirst reply is 'follow_up', and any send after that reply is 'reply'. Derived, never stored\nper message.",
        +              "enum": [
        +                "first",
        +                "follow_up",
        +                "reply"
        +              ],
        +              "title": "Position",
        +              "type": "string"
        +            },
        +            "minItems": 1,
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "senders": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      }
        +    },
        +    "title": "Criteria",
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
    • Changedupdate_segment_group1 field changed
      • changedInput schema / properties / criteria / anyOf
        Previous value: -[
        -  {
        -    "description": "The persisted classify scope a segment tracks — campaigns and teammates (a person has no\nintrinsic channel, so channel is a view filter, never a classify criterion). Each axis null =\nall (every campaign / every teammate). Omit the whole object to leave criteria unset (create)\nor unchanged (edit).",
        -    "properties": {
        -      "agent_ids": {
        -        "anyOf": [
        -          {
        -            "items": {
        -              "type": "integer"
        -            },
        -            "type": "array"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "senders": {
        -        "anyOf": [
        -          {
        -            "items": {
        -              "type": "string"
        -            },
        -            "type": "array"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      }
        -    },
        -    "title": "CriteriaInput",
        -    "type": "object"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "description": "The persisted classify scope a segment tracks — campaigns and teammates (a person has no\nintrinsic channel, so channel is a view filter, never a classify criterion). Each axis null =\nall (every campaign / every teammate). Omit the whole object to leave criteria unset (create)\nor unchanged (edit).",
        +    "properties": {
        +      "agent_ids": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "integer"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "senders": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      }
        +    },
        +    "title": "Criteria",
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
    • Changedupdate_trigger1 field changed
      • changedInput schema / properties / prompt / description
        Previous value: -"New per-run instructions for this trigger."New value: +"New per-run instructions for this trigger. Name any outside resource a run reads (a Google Sheet, doc, file, URL) by the identifier its tool takes (the spreadsheet ID and tab, the URL), not only by its title — a later run doesn't see this chat."
    • Changedupdate_workspace1 field changed
      • changedInput schema / properties / value / description
        Previous value: -"Value to store (required for update; must not be None — use operation='delete' to remove a key).\nFor append, a list of items to add to the existing list."New value: +"Value to store (required for update; must not be None — use operation='delete' to remove a key). Name any outside resource a run reads (a Google Sheet, doc, file, URL) by the identifier its tool takes (the spreadsheet ID and tab, the URL), not only by its title — a later run doesn't see this chat.\nFor append, a list of items to add to the existing list."
  5. 14 tool updates
    • Addedcreate_crm_task
    • Addedcreate_deal
    • Changeddefine_sequence1 field changed
      • changedInput schema / properties / sequence / properties / nodes / items / oneOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
        -    "properties": {
        -      "accepted": {
        -        "type": "string"
        -      },
        -      "already_connected": {
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "connection_request",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "timeout": {
        -        "anyOf": [
        -          {
        -            "additionalProperties": false,
        -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        -            "properties": {
        -              "delay": {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              "to": {
        -                "type": "string"
        -              }
        -            },
        -            "required": [
        -              "delay",
        -              "to"
        -            ],
        -            "title": "TimedAdvance",
        -            "type": "object"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "accepted",
        -      "already_connected"
        -    ],
        -    "title": "ConnectionRequestNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).\n\n`message_template` is input-only: excluded from every dump, so it never lands in the stored\nsequence — the step's template lives in its own rows and is merged back in on read.",
        -    "properties": {
        -      "action": {
        -        "enum": [
        -          "message",
        -          "inmail",
        -          "email"
        -        ],
        -        "type": "string"
        -      },
        -      "channel": {
        -        "enum": [
        -          "linkedin",
        -          "email"
        -        ],
        -        "type": "string"
        -      },
        -      "follow_up": {
        -        "anyOf": [
        -          {
        -            "additionalProperties": false,
        -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        -            "properties": {
        -              "delay": {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              "to": {
        -                "type": "string"
        -              }
        -            },
        -            "required": [
        -              "delay",
        -              "to"
        -            ],
        -            "title": "TimedAdvance",
        -            "type": "object"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "send",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "message_template": {
        -        "anyOf": [
        -          {
        -            "type": "string"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "replied": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "channel",
        -      "action",
        -      "replied"
        -    ],
        -    "title": "SendNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
        -    "properties": {
        -      "action": {
        -        "enum": [
        -          "follow",
        -          "withdraw",
        -          "resolve",
        -          "comment",
        -          "reaction"
        -        ],
        -        "type": "string"
        -      },
        -      "channel": {
        -        "const": "linkedin",
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "action",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "notify": {
        -        "default": false,
        -        "type": "boolean"
        -      },
        -      "then": {
        -        "additionalProperties": false,
        -        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
        -        "properties": {
        -          "after": {
        -            "anyOf": [
        -              {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              {
        -                "type": "null"
        -              }
        -            ],
        -            "default": null
        -          },
        -          "to": {
        -            "type": "string"
        -          }
        -        },
        -        "required": [
        -          "to"
        -        ],
        -        "title": "Advance",
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "channel",
        -      "action",
        -      "then"
        -    ],
        -    "title": "SilentActionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
        -    "properties": {
        -      "action_description": {
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "manual",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "then": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "action_description",
        -      "then"
        -    ],
        -    "title": "ManualActionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a terminal node's hook — not a field here. Unlike a manual node\nit boots an agent run (rather than waiting on a human) and unlike a terminal hook it\nadvances; structurally it is a decision with one implicit 'always' arm — the run does the\nside-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
        -    "properties": {
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "automation",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "then": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "then"
        -    ],
        -    "title": "AutomationNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
        -    "properties": {
        -      "arms": {
        -        "items": {
        -          "additionalProperties": false,
        -          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
        -          "properties": {
        -            "case": {
        -              "minLength": 1,
        -              "type": "string"
        -            },
        -            "to": {
        -              "type": "string"
        -            }
        -          },
        -          "required": [
        -            "case",
        -            "to"
        -          ],
        -          "title": "Arm",
        -          "type": "object"
        -        },
        -        "minItems": 1,
        -        "type": "array"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "decision",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "rule": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "rule",
        -      "arms"
        -    ],
        -    "title": "DecisionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
        -    "properties": {
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "terminal",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind"
        -    ],
        -    "title": "TerminalNode",
        -    "type": "object"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
        +    "properties": {
        +      "accepted": {
        +        "type": "string"
        +      },
        +      "already_connected": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "connection_request",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "timeout": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        +            "properties": {
        +              "delay": {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              "to": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "delay",
        +              "to"
        +            ],
        +            "title": "TimedAdvance",
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "accepted",
        +      "already_connected"
        +    ],
        +    "title": "ConnectionRequestNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).\n\n`message_templates` is input-only: excluded from every dump, so it never lands in the stored\nsequence — the step's templates live in their own rows and are merged back in on read. A step\nwith two is running an A/B test.",
        +    "properties": {
        +      "action": {
        +        "enum": [
        +          "message",
        +          "inmail",
        +          "email"
        +        ],
        +        "type": "string"
        +      },
        +      "channel": {
        +        "enum": [
        +          "linkedin",
        +          "email"
        +        ],
        +        "type": "string"
        +      },
        +      "follow_up": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        +            "properties": {
        +              "delay": {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              "to": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "delay",
        +              "to"
        +            ],
        +            "title": "TimedAdvance",
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "send",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "message_templates": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "string"
        +            },
        +            "maxItems": 2,
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "replied": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "channel",
        +      "action",
        +      "replied"
        +    ],
        +    "title": "SendNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
        +    "properties": {
        +      "action": {
        +        "enum": [
        +          "follow",
        +          "withdraw",
        +          "resolve",
        +          "comment",
        +          "reaction"
        +        ],
        +        "type": "string"
        +      },
        +      "channel": {
        +        "const": "linkedin",
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "action",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "notify": {
        +        "default": false,
        +        "type": "boolean"
        +      },
        +      "then": {
        +        "additionalProperties": false,
        +        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
        +        "properties": {
        +          "after": {
        +            "anyOf": [
        +              {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "default": null
        +          },
        +          "to": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "to"
        +        ],
        +        "title": "Advance",
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "channel",
        +      "action",
        +      "then"
        +    ],
        +    "title": "SilentActionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
        +    "properties": {
        +      "action_description": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "manual",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "then": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "action_description",
        +      "then"
        +    ],
        +    "title": "ManualActionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a terminal node's hook — not a field here. Unlike a manual node\nit boots an agent run (rather than waiting on a human) and unlike a terminal hook it\nadvances; structurally it is a decision with one implicit 'always' arm — the run does the\nside-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
        +    "properties": {
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "automation",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "then": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "then"
        +    ],
        +    "title": "AutomationNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
        +    "properties": {
        +      "arms": {
        +        "items": {
        +          "additionalProperties": false,
        +          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
        +          "properties": {
        +            "case": {
        +              "minLength": 1,
        +              "type": "string"
        +            },
        +            "to": {
        +              "type": "string"
        +            }
        +          },
        +          "required": [
        +            "case",
        +            "to"
        +          ],
        +          "title": "Arm",
        +          "type": "object"
        +        },
        +        "minItems": 1,
        +        "type": "array"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "decision",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "rule": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "rule",
        +      "arms"
        +    ],
        +    "title": "DecisionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
        +    "properties": {
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "terminal",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind"
        +    ],
        +    "title": "TerminalNode",
        +    "type": "object"
        +  }
        +]
    • Addedget_message_experiments
    • Changedmatches_icp4 fields changed
      • removedInput schema / properties / icp_locations
        Removed value: -{
        -  "description": "Configured ICP location strings.",
        -  "items": {
        -    "type": "string"
        -  },
        -  "type": "array"
        -}
      • removedInput schema / properties / location
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "string"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Candidate location (Apollo enriched.location, or TheirStack\ncity/country). None when enrichment had none."
        -}
      • addedInput schema / properties / location_verdict
        Added value: +{
        +  "description": "The agent's judgment of whether the candidate's HQ lies\nwithin the configured ICP locations — 'in', 'uncertain' (plausibly\nin, but the location data is partial or it sits just outside a listed\nplace — kept and flagged), or 'out' (drop).",
        +  "enum": [
        +    "in",
        +    "uncertain",
        +    "out"
        +  ],
        +  "type": "string"
        +}
      • changedInput schema / required
        Previous value: -[
        -  "industry_verdict",
        -  "icp_locations",
        -  "icp_employee_count_ranges"
        -]New value: +[
        +  "industry_verdict",
        +  "location_verdict",
        +  "icp_employee_count_ranges"
        +]
    • Addedquery_crm_tasks
    • Addedquery_deals
    • Changedquery_people1 field changed
      • changedInput schema / properties / group_by / description
        Previous value: -"Aggregate mode, returned instead of the row list — whole-set per-bucket counts\nover all your people (not capped by `limit`). \"title\" / \"company\" / \"location\" bucket\nby that firmographic — \"title\" falls back to the headline when the title is blank, so\nlist a title bucket's people with \"COALESCE(NULLIF(title, ''), headline) = '<key>'\";\n\"outreach_stage\" by the person's collapsed lead-funnel bucket;\n\"segment\" by the person's tag in the `segment_group_id` group. Omit for the row list."New value: +"Aggregate mode, returned instead of the row list — whole-set per-bucket counts\nover all your people (not capped by `limit`). \"title\" / \"company\" / \"location\" bucket\nby that column's value; \"outreach_stage\" by the person's collapsed lead-funnel bucket;\n\"segment\" by the person's tag in the `segment_group_id` group. Omit for the row list."
    • Changedquery_task_people2 fields changed
      • changedInput schema / properties / filters / description
        Previous value: -"Clauses that must all match. For \"degree\", \"outreach_stage\", \"list\" or\n\"source\", use {\"column_key\": <dimension>, \"operator\": \"is_any_of\", \"values\": [keys]}\nwith keys as group_counts returns them ('' matches no value). For \"name\", \"title\",\n\"company\" or \"location\", use {\"column_key\": <column>, \"operator\": \"contains\",\n\"text\": <substring>}."New value: +"Clauses that must all match. For \"degree\", \"outreach_stage\", \"list\" or\n\"source\", use {\"column_key\": <dimension>, \"operator\": \"is_any_of\", \"values\": [keys]}\nwith keys as group_counts returns them ('' matches no value). For \"name\", \"title\",\n\"headline\", \"company\" or \"location\", use {\"column_key\": <column>, \"operator\":\n\"contains\", \"text\": <substring>}."
      • changedInput schema / properties / q / description
        Previous value: -"A substring matched against name, title and company."New value: +"A substring matched against name, title, headline and company."
    • Changedsetup_linkedin_sequence1 field changed
      • changedInput schema / properties / identifiers / description
        Previous value: -"List of recipient dicts. Each dict must carry one of\n`linkedin_url` (a profile URL or slug), `provider_id`, or `chat_id`,\ncopied verbatim from the tool result that surfaced the person — never a\nslug rebuilt from a display name, which enrolls the wrong person.\nFreeform attributes go under `data`; unknown top-level keys are\nrejected. If you have no identifier for someone, omit them rather than\nguess. When a recipient's\n`target_name` shares no tokens with the verified (resolved) name\nalready tracked for that `provider_id`, the whole batch is\nrejected with ModelRetry before anything queues — fix the pairing\nand re-send (use the person's LinkedIn URL/identifier if the pid\nis wrong; use one consistent name if it's the same person). An\nunverified pid isn't gated here — it resolves before send and the\nresolver adjudicates the name then."New value: +"List of recipient dicts. Each dict must carry one of\n`linkedin_url` (a profile URL or slug), `provider_id`, or `chat_id`,\ncopied verbatim from the tool result that surfaced the person — never a\nslug rebuilt from a display name, which enrolls the wrong person.\nFreeform attributes go under `data`; unknown top-level keys are\nrejected. If you have no identifier for someone, omit them rather than\nguess. When a recipient's\n`target_name` shares no tokens with any LinkedIn name already held\nfor that `provider_id` (a verified prospect or a post engager), the\nwhole batch is rejected with ModelRetry before anything queues —\nfix the pairing and re-send (use the person's LinkedIn\nURL/identifier if the pid is wrong; use one consistent name if it's\nthe same person). A pid with no held name isn't gated here — it\nresolves before send and the resolver adjudicates the name then."
    • Changedtrack_prospects1 field changed
      • changedInput schema / properties / items / description
        Previous value: -"People to add (see ProspectItem). Each carries a `person` with the\nidentifiers you already hold (LinkedIn URL / slug, email, or\nprovider_id) copied verbatim from the tool result that surfaced them —\nnever a slug rebuilt from a display name, which resolves to the wrong\nperson. Junk like a company URL or 'N/A' is rejected. The display name\ngoes on `person.display_name`; optional freeform `data` per item."New value: +"People to add (see ProspectItem). Each carries a `person` with the\nidentifiers you already hold (LinkedIn URL / slug, email, or\nprovider_id) copied verbatim from the tool result that surfaced them —\nnever a slug rebuilt from a display name, which resolves to the wrong\nperson. Junk like a company URL or 'N/A' is rejected. The display name\ngoes on `person.display_name`, taken from the same row as the identifier\n— a name that shares no token with the LinkedIn name already on file for\nits ID rejects the whole call. Optional freeform `data` per item."
    • Addedupdate_crm_task
    • Addedupdate_deal
    • Changedupdate_node2 fields changed
      • changedInput schema / properties / node / description
        Previous value: -"The node's complete new definition — an omitted optional slot (a `follow_up`, a\n`timeout`) is dropped, not preserved, so carry forward every field the node keeps.\nA send node's `message_template` is the exception: omitted leaves its template as it is.\nIts `id` must name an existing body node and its `kind` must match that node's\ncurrent kind."New value: +"The node's complete new definition — an omitted optional slot (a `follow_up`, a\n`timeout`) is dropped, not preserved, so carry forward every field the node keeps.\nA send node's `message_templates` is the exception: omitted leaves the step's\ntemplates as they are.\nIts `id` must name an existing body node and its `kind` must match that node's\ncurrent kind."
      • changedInput schema / properties / node / oneOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
        -    "properties": {
        -      "accepted": {
        -        "type": "string"
        -      },
        -      "already_connected": {
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "connection_request",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "timeout": {
        -        "anyOf": [
        -          {
        -            "additionalProperties": false,
        -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        -            "properties": {
        -              "delay": {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              "to": {
        -                "type": "string"
        -              }
        -            },
        -            "required": [
        -              "delay",
        -              "to"
        -            ],
        -            "title": "TimedAdvance",
        -            "type": "object"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "accepted",
        -      "already_connected"
        -    ],
        -    "title": "ConnectionRequestNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).\n\n`message_template` is input-only: excluded from every dump, so it never lands in the stored\nsequence — the step's template lives in its own rows and is merged back in on read.",
        -    "properties": {
        -      "action": {
        -        "enum": [
        -          "message",
        -          "inmail",
        -          "email"
        -        ],
        -        "type": "string"
        -      },
        -      "channel": {
        -        "enum": [
        -          "linkedin",
        -          "email"
        -        ],
        -        "type": "string"
        -      },
        -      "follow_up": {
        -        "anyOf": [
        -          {
        -            "additionalProperties": false,
        -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        -            "properties": {
        -              "delay": {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              "to": {
        -                "type": "string"
        -              }
        -            },
        -            "required": [
        -              "delay",
        -              "to"
        -            ],
        -            "title": "TimedAdvance",
        -            "type": "object"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "send",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "message_template": {
        -        "anyOf": [
        -          {
        -            "type": "string"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "replied": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "channel",
        -      "action",
        -      "replied"
        -    ],
        -    "title": "SendNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
        -    "properties": {
        -      "action": {
        -        "enum": [
        -          "follow",
        -          "withdraw",
        -          "resolve",
        -          "comment",
        -          "reaction"
        -        ],
        -        "type": "string"
        -      },
        -      "channel": {
        -        "const": "linkedin",
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "action",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "notify": {
        -        "default": false,
        -        "type": "boolean"
        -      },
        -      "then": {
        -        "additionalProperties": false,
        -        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
        -        "properties": {
        -          "after": {
        -            "anyOf": [
        -              {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              {
        -                "type": "null"
        -              }
        -            ],
        -            "default": null
        -          },
        -          "to": {
        -            "type": "string"
        -          }
        -        },
        -        "required": [
        -          "to"
        -        ],
        -        "title": "Advance",
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "channel",
        -      "action",
        -      "then"
        -    ],
        -    "title": "SilentActionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
        -    "properties": {
        -      "action_description": {
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "manual",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "then": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "action_description",
        -      "then"
        -    ],
        -    "title": "ManualActionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a terminal node's hook — not a field here. Unlike a manual node\nit boots an agent run (rather than waiting on a human) and unlike a terminal hook it\nadvances; structurally it is a decision with one implicit 'always' arm — the run does the\nside-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
        -    "properties": {
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "automation",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "then": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "then"
        -    ],
        -    "title": "AutomationNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
        -    "properties": {
        -      "arms": {
        -        "items": {
        -          "additionalProperties": false,
        -          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
        -          "properties": {
        -            "case": {
        -              "minLength": 1,
        -              "type": "string"
        -            },
        -            "to": {
        -              "type": "string"
        -            }
        -          },
        -          "required": [
        -            "case",
        -            "to"
        -          ],
        -          "title": "Arm",
        -          "type": "object"
        -        },
        -        "minItems": 1,
        -        "type": "array"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "decision",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "rule": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "rule",
        -      "arms"
        -    ],
        -    "title": "DecisionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
        -    "properties": {
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "terminal",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind"
        -    ],
        -    "title": "TerminalNode",
        -    "type": "object"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
        +    "properties": {
        +      "accepted": {
        +        "type": "string"
        +      },
        +      "already_connected": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "connection_request",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "timeout": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        +            "properties": {
        +              "delay": {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              "to": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "delay",
        +              "to"
        +            ],
        +            "title": "TimedAdvance",
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "accepted",
        +      "already_connected"
        +    ],
        +    "title": "ConnectionRequestNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).\n\n`message_templates` is input-only: excluded from every dump, so it never lands in the stored\nsequence — the step's templates live in their own rows and are merged back in on read. A step\nwith two is running an A/B test.",
        +    "properties": {
        +      "action": {
        +        "enum": [
        +          "message",
        +          "inmail",
        +          "email"
        +        ],
        +        "type": "string"
        +      },
        +      "channel": {
        +        "enum": [
        +          "linkedin",
        +          "email"
        +        ],
        +        "type": "string"
        +      },
        +      "follow_up": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        +            "properties": {
        +              "delay": {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              "to": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "delay",
        +              "to"
        +            ],
        +            "title": "TimedAdvance",
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "send",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "message_templates": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "string"
        +            },
        +            "maxItems": 2,
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "replied": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "channel",
        +      "action",
        +      "replied"
        +    ],
        +    "title": "SendNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
        +    "properties": {
        +      "action": {
        +        "enum": [
        +          "follow",
        +          "withdraw",
        +          "resolve",
        +          "comment",
        +          "reaction"
        +        ],
        +        "type": "string"
        +      },
        +      "channel": {
        +        "const": "linkedin",
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "action",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "notify": {
        +        "default": false,
        +        "type": "boolean"
        +      },
        +      "then": {
        +        "additionalProperties": false,
        +        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
        +        "properties": {
        +          "after": {
        +            "anyOf": [
        +              {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "default": null
        +          },
        +          "to": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "to"
        +        ],
        +        "title": "Advance",
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "channel",
        +      "action",
        +      "then"
        +    ],
        +    "title": "SilentActionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
        +    "properties": {
        +      "action_description": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "manual",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "then": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "action_description",
        +      "then"
        +    ],
        +    "title": "ManualActionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a terminal node's hook — not a field here. Unlike a manual node\nit boots an agent run (rather than waiting on a human) and unlike a terminal hook it\nadvances; structurally it is a decision with one implicit 'always' arm — the run does the\nside-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
        +    "properties": {
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "automation",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "then": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "then"
        +    ],
        +    "title": "AutomationNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
        +    "properties": {
        +      "arms": {
        +        "items": {
        +          "additionalProperties": false,
        +          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
        +          "properties": {
        +            "case": {
        +              "minLength": 1,
        +              "type": "string"
        +            },
        +            "to": {
        +              "type": "string"
        +            }
        +          },
        +          "required": [
        +            "case",
        +            "to"
        +          ],
        +          "title": "Arm",
        +          "type": "object"
        +        },
        +        "minItems": 1,
        +        "type": "array"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "decision",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "rule": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "rule",
        +      "arms"
        +    ],
        +    "title": "DecisionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
        +    "properties": {
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "terminal",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind"
        +    ],
        +    "title": "TerminalNode",
        +    "type": "object"
        +  }
        +]
  6. 11 tool updates
    • Changedclassify_message_tag_group1 field changed
      • removedInput schema / properties / senders
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "items": {
        -        "type": "string"
        -      },
        -      "type": "array"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "In a shared workspace, the teammate email addresses whose sent messages to\ntag (and charge for). Omit to tag every sender in the workspace. Any email not in\nthe workspace is dropped; if that leaves no valid teammate, nothing is tagged — it\ndoes NOT fall back to the whole workspace."
        -}
    • Changedclassify_segment_group1 field changed
      • removedInput schema / properties / senders
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "items": {
        -        "type": "string"
        -      },
        -      "type": "array"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "In a shared workspace, the teammate email addresses whose people to tag — people\nwith a prospect owned by one of them. Omit to tag everyone in scope (including people\nno teammate has a prospect for). Addresses outside the workspace match nobody."
        -}
    • Changedcreate_message_tag_group2 fields changed
      • changedInput schema / properties / criteria / anyOf
        Previous value: -[
        -  {
        -    "description": "The persisted classify scope a question tracks. Each axis null = all (every channel /\nevery campaign / first messages and follow-ups). Omit the whole object to leave criteria unset\n(create) or unchanged (edit).",
        -    "properties": {
        -      "agent_ids": {
        -        "anyOf": [
        -          {
        -            "items": {
        -              "type": "integer"
        -            },
        -            "type": "array"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "channel": {
        -        "anyOf": [
        -          {
        -            "description": "The outreach channel a query is bounded to; None means both.",
        -            "enum": [
        -              "linkedin",
        -              "email"
        -            ],
        -            "title": "Channel",
        -            "type": "string"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "position": {
        -        "anyOf": [
        -          {
        -            "enum": [
        -              "first",
        -              "follow_up"
        -            ],
        -            "type": "string"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      }
        -    },
        -    "title": "CriteriaInput",
        -    "type": "object"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "description": "The persisted classify scope a question tracks. Each axis null = all (every channel /\nevery campaign / every conversation position / every teammate). Omit the whole object to leave\ncriteria unset (create) or unchanged (edit).",
        +    "properties": {
        +      "agent_ids": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "integer"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "channel": {
        +        "anyOf": [
        +          {
        +            "description": "The outreach channel a query is bounded to; None means both.",
        +            "enum": [
        +              "linkedin",
        +              "email"
        +            ],
        +            "title": "Channel",
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "positions": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "description": "A send's spot in its conversation; a positions filter is a list of these, None for all.\nThe conversation's first outbound send is 'first', a later one sent before the prospect's\nfirst reply is 'follow_up', and any send after that reply is 'reply'. Derived, never stored\nper message.",
        +              "enum": [
        +                "first",
        +                "follow_up",
        +                "reply"
        +              ],
        +              "title": "Position",
        +              "type": "string"
        +            },
        +            "minItems": 1,
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "senders": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      }
        +    },
        +    "title": "CriteriaInput",
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / criteria / description
        Previous value: -"The classify scope this question tracks — {channel, agent_ids, position}, each\nnull = all. `channel` is 'linkedin' or 'email' (null = both); `agent_ids` are campaign\n(agent_tasks) ids from classifiable_campaigns (null = every campaign); `position` is\n'first' (each conversation's first message) or 'follow_up' (later messages sent before\nthe prospect replied), null = both. classify reuses this scope, so set it to what the\nquestion should track. Omit for all."New value: +"The classify scope this question tracks — {channel, agent_ids, positions},\neach null = all. `channel` is 'linkedin' or 'email' (null = both); `agent_ids` are\ncampaign (agent_tasks) ids from classifiable_campaigns (null = every campaign);\n`positions` lists the conversation positions to tag — 'first' (each conversation's\nfirst message), 'follow_up' (later messages sent before the prospect replied), and/or\n'reply' (messages sent after the prospect replied); null = all. `senders` are the\nteammate emails whose sent messages to tag (null = every teammate); in a shared\nworkspace, default it to the user's own email unless they ask for teammates. classify\nreuses this scope, so set it to what the question should track. Omit for all."
    • Changedcreate_segment_group2 fields changed
      • changedInput schema / properties / criteria / anyOf
        Previous value: -[
        -  {
        -    "description": "The persisted classify scope a segment tracks — campaigns only (a person has no intrinsic\nchannel, so channel is a view filter, never a classify criterion). `agent_ids` null = every\ncampaign. Omit the whole object to leave criteria unset (create) or unchanged (edit).",
        -    "properties": {
        -      "agent_ids": {
        -        "anyOf": [
        -          {
        -            "items": {
        -              "type": "integer"
        -            },
        -            "type": "array"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      }
        -    },
        -    "title": "CriteriaInput",
        -    "type": "object"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "description": "The persisted classify scope a segment tracks — campaigns and teammates (a person has no\nintrinsic channel, so channel is a view filter, never a classify criterion). Each axis null =\nall (every campaign / every teammate). Omit the whole object to leave criteria unset (create)\nor unchanged (edit).",
        +    "properties": {
        +      "agent_ids": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "integer"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "senders": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      }
        +    },
        +    "title": "CriteriaInput",
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / criteria / description
        Previous value: -"The classify scope this segment tracks — {agent_ids}, the campaign (agent_tasks)\nids from classifiable_campaigns (null = every campaign). classify reuses this scope, so\nset it to what the segment should track. Omit for every campaign."New value: +"The classify scope this segment tracks — {agent_ids, senders}: the campaign\n(agent_tasks) ids from classifiable_campaigns and the teammate emails whose prospects\nto tag, each null = all. classify reuses this scope, so set it to what the segment\nshould track; in a shared workspace, default `senders` to the user's own email unless\nthey ask for teammates. Omit for every campaign and teammate."
    • Changedestimate_message_tag_classify_scope2 fields changed
      • removedInput schema / properties / position
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "enum": [
        -        "first",
        -        "follow_up"
        -      ],
        -      "type": "string"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "'first' (each conversation's first message) or 'follow_up' (later messages sent\nbefore the prospect replied) — the value you'd pass as `criteria.position`. Omit for\nboth."
        -}
      • addedInput schema / properties / positions
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "description": "A send's spot in its conversation; a positions filter is a list of these, None for all.\nThe conversation's first outbound send is 'first', a later one sent before the prospect's\nfirst reply is 'follow_up', and any send after that reply is 'reply'. Derived, never stored\nper message.",
        +        "enum": [
        +          "first",
        +          "follow_up",
        +          "reply"
        +        ],
        +        "title": "Position",
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "The conversation positions to count — 'first' (each conversation's first\nmessage), 'follow_up' (later messages sent before the prospect replied), and/or 'reply'\n(messages sent after the prospect replied) — the value you'd pass as\n`criteria.positions`. Omit for all."
        +}
    • Changedestimate_segment_classify_scope1 field changed
      • addedInput schema / properties / senders
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "The teammate emails whose people the first run would tag, as you'd pass to\nclassify_segment_group. Omit for everyone."
        +}
    • Changedget_message_tag_rates2 fields changed
      • removedInput schema / properties / position
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "enum": [
        -        "first",
        -        "follow_up"
        -      ],
        -      "type": "string"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "'first' (each conversation's first message) or 'follow_up' (later messages\nsent before the prospect replied); omit for both."
        -}
      • addedInput schema / properties / positions
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "description": "A send's spot in its conversation; a positions filter is a list of these, None for all.\nThe conversation's first outbound send is 'first', a later one sent before the prospect's\nfirst reply is 'follow_up', and any send after that reply is 'reply'. Derived, never stored\nper message.",
        +        "enum": [
        +          "first",
        +          "follow_up",
        +          "reply"
        +        ],
        +        "title": "Position",
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "The conversation positions to score — 'first' (each conversation's first\nmessage), 'follow_up' (later messages sent before the prospect replied), and/or 'reply'\n(messages sent after the prospect replied); omit for all."
        +}
    • Changedquery_tagged_messages2 fields changed
      • removedInput schema / properties / position
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "enum": [
        -        "first",
        -        "follow_up"
        -      ],
        -      "type": "string"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "'first' (each conversation's first message) or 'follow_up' (later messages\nsent before the prospect replied); omit for both."
        -}
      • addedInput schema / properties / positions
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "description": "A send's spot in its conversation; a positions filter is a list of these, None for all.\nThe conversation's first outbound send is 'first', a later one sent before the prospect's\nfirst reply is 'follow_up', and any send after that reply is 'reply'. Derived, never stored\nper message.",
        +        "enum": [
        +          "first",
        +          "follow_up",
        +          "reply"
        +        ],
        +        "title": "Position",
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "The conversation positions to return — 'first' (each conversation's first\nmessage), 'follow_up' (later messages sent before the prospect replied), and/or 'reply'\n(messages sent after the prospect replied); omit for all."
        +}
    • Addedquery_task_people
    • Changedupdate_message_tag_group2 fields changed
      • changedInput schema / properties / criteria / anyOf
        Previous value: -[
        -  {
        -    "description": "The persisted classify scope a question tracks. Each axis null = all (every channel /\nevery campaign / first messages and follow-ups). Omit the whole object to leave criteria unset\n(create) or unchanged (edit).",
        -    "properties": {
        -      "agent_ids": {
        -        "anyOf": [
        -          {
        -            "items": {
        -              "type": "integer"
        -            },
        -            "type": "array"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "channel": {
        -        "anyOf": [
        -          {
        -            "description": "The outreach channel a query is bounded to; None means both.",
        -            "enum": [
        -              "linkedin",
        -              "email"
        -            ],
        -            "title": "Channel",
        -            "type": "string"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "position": {
        -        "anyOf": [
        -          {
        -            "enum": [
        -              "first",
        -              "follow_up"
        -            ],
        -            "type": "string"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      }
        -    },
        -    "title": "CriteriaInput",
        -    "type": "object"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "description": "The persisted classify scope a question tracks. Each axis null = all (every channel /\nevery campaign / every conversation position / every teammate). Omit the whole object to leave\ncriteria unset (create) or unchanged (edit).",
        +    "properties": {
        +      "agent_ids": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "integer"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "channel": {
        +        "anyOf": [
        +          {
        +            "description": "The outreach channel a query is bounded to; None means both.",
        +            "enum": [
        +              "linkedin",
        +              "email"
        +            ],
        +            "title": "Channel",
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "positions": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "description": "A send's spot in its conversation; a positions filter is a list of these, None for all.\nThe conversation's first outbound send is 'first', a later one sent before the prospect's\nfirst reply is 'follow_up', and any send after that reply is 'reply'. Derived, never stored\nper message.",
        +              "enum": [
        +                "first",
        +                "follow_up",
        +                "reply"
        +              ],
        +              "title": "Position",
        +              "type": "string"
        +            },
        +            "minItems": 1,
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "senders": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      }
        +    },
        +    "title": "CriteriaInput",
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / criteria / description
        Previous value: -"The classify scope to track — {channel, agent_ids, position}, each null = all\n(see create_message_tag_group). A provided object REPLACES every axis; omit to leave\nthe stored criteria unchanged. classify reuses this scope, so change it to re-target\nwhat the question tracks."New value: +"The classify scope to track — {channel, agent_ids, positions, senders}, each\nnull = all (see create_message_tag_group). A provided object REPLACES every axis (an\naxis you leave out becomes all), so pass the current value of any axis you keep; omit\nthe object to leave the stored criteria unchanged. classify reuses this scope, so change it to re-target\nwhat the question tracks."
    • Changedupdate_segment_group2 fields changed
      • changedInput schema / properties / criteria / anyOf
        Previous value: -[
        -  {
        -    "description": "The persisted classify scope a segment tracks — campaigns only (a person has no intrinsic\nchannel, so channel is a view filter, never a classify criterion). `agent_ids` null = every\ncampaign. Omit the whole object to leave criteria unset (create) or unchanged (edit).",
        -    "properties": {
        -      "agent_ids": {
        -        "anyOf": [
        -          {
        -            "items": {
        -              "type": "integer"
        -            },
        -            "type": "array"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      }
        -    },
        -    "title": "CriteriaInput",
        -    "type": "object"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "description": "The persisted classify scope a segment tracks — campaigns and teammates (a person has no\nintrinsic channel, so channel is a view filter, never a classify criterion). Each axis null =\nall (every campaign / every teammate). Omit the whole object to leave criteria unset (create)\nor unchanged (edit).",
        +    "properties": {
        +      "agent_ids": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "integer"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "senders": {
        +        "anyOf": [
        +          {
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      }
        +    },
        +    "title": "CriteriaInput",
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / criteria / description
        Previous value: -"The classify scope to track — {agent_ids}, the campaign (agent_tasks) ids (null =\nevery campaign). A provided object REPLACES the stored scope; omit to leave it unchanged.\nclassify reuses this scope, so change it to re-target what the segment tracks."New value: +"The classify scope to track — {agent_ids, senders}, the campaign (agent_tasks)\nids and teammate emails, each null = all. A provided object REPLACES the whole stored\nscope (an axis you leave out becomes all), so pass the current value of any axis you\nkeep; omit the object to leave it unchanged. classify reuses this scope, so change it\nto re-target what the segment tracks."
  7. 79 tool updates
    • Changedadd_trigger3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the agent to add the trigger to",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the task to add the trigger to",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "trigger"
        -]New value: +[
        +  "agent_id",
        +  "trigger"
        +]
    • Changedappend_learning3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the agent",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the task",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id"
        -]New value: +[
        +  "agent_id"
        +]
    • Changedclassify_message_tag_group2 fields changed
      • addedInput schema / properties / agent_ids
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "integer"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Scope classification to specific campaigns (agent_tasks ids from a group's\n`classifiable_campaigns`); omit for every campaign. `[]` classifies zero."
        +}
      • removedInput schema / properties / task_ids
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "items": {
        -        "type": "integer"
        -      },
        -      "type": "array"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Scope classification to specific campaigns (agent_tasks ids from a group's\n`classifiable_campaigns`); omit for every campaign. `[]` classifies zero."
        -}
    • Changedclassify_segment_group1 field changed
      • addedInput schema / properties / senders
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "In a shared workspace, the teammate email addresses whose people to tag — people\nwith a prospect owned by one of them. Omit to tag everyone in scope (including people\nno teammate has a prospect for). Addresses outside the workspace match nobody."
        +}
    • Changedcorrect_message_classification1 field changed
      • changedInput schema / properties / tag_ids / description
        Previous value: -"The corrected tag ids (from the group's tags). At most one for a single-tag\ncomparison; an empty list clears the message to \"no tag\"."New value: +"The corrected tag ids (from the group's tags). At most one for a single-tag\ncomparison; an empty list clears the message to \"No match\"."
    • Changedcorrect_segment_classification1 field changed
      • changedInput schema / properties / tag_ids / description
        Previous value: -"The corrected tag ids (from the group's tags). At most one for a single-tag group;\nan empty list clears the person to \"no tag\"."New value: +"The corrected tag ids (from the group's tags). At most one for a single-tag group;\nan empty list clears the person to \"No match\"."
    • Changedcreate_agent4 fields changed
      • changedInput schema / properties / goal / description
        Previous value: -"Complete instructions for what the task should accomplish. Write as if instructing another assistant."New value: +"Complete instructions for what the agent should accomplish. Write as if instructing another assistant."
      • changedInput schema / properties / title / description
        Previous value: -"Short name for the task (e.g. \"Outreach to Q2 leads\", \"Email triage\")"New value: +"Short name for the agent (e.g. \"Outreach to Q2 leads\", \"Email triage\")"
      • changedInput schema / properties / triggers / description
        Previous value: -"List of triggers that determine when the task runs. A task can have multiple triggers of different types (e.g. one schedule plus one event handler) — they fire independently. At least one trigger is required."New value: +"List of triggers that determine when the agent runs. An agent can have multiple triggers of different types (e.g. one schedule plus one event handler) — they fire independently. At least one trigger is required."
      • changedInput schema / properties / workspace / description
        Previous value: -"Optional `{'inputs': {...}, 'outputs': {...}}` dict that seeds the task's workspace."New value: +"Optional `{'inputs': {...}, 'outputs': {...}}` dict that seeds the agent's workspace."
    • Changedcreate_message_tag_group1 field changed
      • addedInput schema / properties / criteria
        Added value: +{
        +  "anyOf": [
        +    {
        +      "description": "The persisted classify scope a question tracks. Each axis null = all (every channel /\nevery campaign / first messages and follow-ups). Omit the whole object to leave criteria unset\n(create) or unchanged (edit).",
        +      "properties": {
        +        "agent_ids": {
        +          "anyOf": [
        +            {
        +              "items": {
        +                "type": "integer"
        +              },
        +              "type": "array"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ],
        +          "default": null
        +        },
        +        "channel": {
        +          "anyOf": [
        +            {
        +              "description": "The outreach channel a query is bounded to; None means both.",
        +              "enum": [
        +                "linkedin",
        +                "email"
        +              ],
        +              "title": "Channel",
        +              "type": "string"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ],
        +          "default": null
        +        },
        +        "position": {
        +          "anyOf": [
        +            {
        +              "enum": [
        +                "first",
        +                "follow_up"
        +              ],
        +              "type": "string"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ],
        +          "default": null
        +        }
        +      },
        +      "title": "CriteriaInput",
        +      "type": "object"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "The classify scope this question tracks — {channel, agent_ids, position}, each\nnull = all. `channel` is 'linkedin' or 'email' (null = both); `agent_ids` are campaign\n(agent_tasks) ids from classifiable_campaigns (null = every campaign); `position` is\n'first' (each conversation's first message) or 'follow_up' (later messages sent before\nthe prospect replied), null = both. classify reuses this scope, so set it to what the\nquestion should track. Omit for all."
        +}
    • Changedcreate_segment_group1 field changed
      • addedInput schema / properties / criteria
        Added value: +{
        +  "anyOf": [
        +    {
        +      "description": "The persisted classify scope a segment tracks — campaigns only (a person has no intrinsic\nchannel, so channel is a view filter, never a classify criterion). `agent_ids` null = every\ncampaign. Omit the whole object to leave criteria unset (create) or unchanged (edit).",
        +      "properties": {
        +        "agent_ids": {
        +          "anyOf": [
        +            {
        +              "items": {
        +                "type": "integer"
        +              },
        +              "type": "array"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ],
        +          "default": null
        +        }
        +      },
        +      "title": "CriteriaInput",
        +      "type": "object"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "The classify scope this segment tracks — {agent_ids}, the campaign (agent_tasks)\nids from classifiable_campaigns (null = every campaign). classify reuses this scope, so\nset it to what the segment should track. Omit for every campaign."
        +}
    • Removedcreate_todo
    • Changeddefine_sequence4 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the agent to attach the sequence to.",
        +  "type": "integer"
        +}
      • changedInput schema / properties / sequence / properties / nodes / items / oneOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
        -    "properties": {
        -      "accepted": {
        -        "type": "string"
        -      },
        -      "already_connected": {
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "connection_request",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "timeout": {
        -        "anyOf": [
        -          {
        -            "additionalProperties": false,
        -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        -            "properties": {
        -              "delay": {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              "to": {
        -                "type": "string"
        -              }
        -            },
        -            "required": [
        -              "delay",
        -              "to"
        -            ],
        -            "title": "TimedAdvance",
        -            "type": "object"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "accepted",
        -      "already_connected"
        -    ],
        -    "title": "ConnectionRequestNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).",
        -    "properties": {
        -      "action": {
        -        "enum": [
        -          "message",
        -          "inmail",
        -          "email"
        -        ],
        -        "type": "string"
        -      },
        -      "channel": {
        -        "enum": [
        -          "linkedin",
        -          "email"
        -        ],
        -        "type": "string"
        -      },
        -      "follow_up": {
        -        "anyOf": [
        -          {
        -            "additionalProperties": false,
        -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        -            "properties": {
        -              "delay": {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              "to": {
        -                "type": "string"
        -              }
        -            },
        -            "required": [
        -              "delay",
        -              "to"
        -            ],
        -            "title": "TimedAdvance",
        -            "type": "object"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "send",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "replied": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "channel",
        -      "action",
        -      "replied"
        -    ],
        -    "title": "SendNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
        -    "properties": {
        -      "action": {
        -        "enum": [
        -          "follow",
        -          "withdraw",
        -          "resolve",
        -          "comment",
        -          "reaction"
        -        ],
        -        "type": "string"
        -      },
        -      "channel": {
        -        "const": "linkedin",
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "action",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "notify": {
        -        "default": false,
        -        "type": "boolean"
        -      },
        -      "then": {
        -        "additionalProperties": false,
        -        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
        -        "properties": {
        -          "after": {
        -            "anyOf": [
        -              {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              {
        -                "type": "null"
        -              }
        -            ],
        -            "default": null
        -          },
        -          "to": {
        -            "type": "string"
        -          }
        -        },
        -        "required": [
        -          "to"
        -        ],
        -        "title": "Advance",
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "channel",
        -      "action",
        -      "then"
        -    ],
        -    "title": "SilentActionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
        -    "properties": {
        -      "action_description": {
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "manual",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "then": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "action_description",
        -      "then"
        -    ],
        -    "title": "ManualActionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a send node's template and a terminal node's hook — not a field\nhere. Unlike a manual node it boots an agent run (rather than waiting on a human) and unlike\na terminal hook it advances; structurally it is a decision with one implicit 'always' arm —\nthe run does the side-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
        -    "properties": {
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "automation",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "then": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "then"
        -    ],
        -    "title": "AutomationNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
        -    "properties": {
        -      "arms": {
        -        "items": {
        -          "additionalProperties": false,
        -          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
        -          "properties": {
        -            "case": {
        -              "minLength": 1,
        -              "type": "string"
        -            },
        -            "to": {
        -              "type": "string"
        -            }
        -          },
        -          "required": [
        -            "case",
        -            "to"
        -          ],
        -          "title": "Arm",
        -          "type": "object"
        -        },
        -        "minItems": 1,
        -        "type": "array"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "decision",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "rule": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "rule",
        -      "arms"
        -    ],
        -    "title": "DecisionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
        -    "properties": {
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "terminal",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind"
        -    ],
        -    "title": "TerminalNode",
        -    "type": "object"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
        +    "properties": {
        +      "accepted": {
        +        "type": "string"
        +      },
        +      "already_connected": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "connection_request",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "timeout": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        +            "properties": {
        +              "delay": {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              "to": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "delay",
        +              "to"
        +            ],
        +            "title": "TimedAdvance",
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "accepted",
        +      "already_connected"
        +    ],
        +    "title": "ConnectionRequestNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).\n\n`message_template` is input-only: excluded from every dump, so it never lands in the stored\nsequence — the step's template lives in its own rows and is merged back in on read.",
        +    "properties": {
        +      "action": {
        +        "enum": [
        +          "message",
        +          "inmail",
        +          "email"
        +        ],
        +        "type": "string"
        +      },
        +      "channel": {
        +        "enum": [
        +          "linkedin",
        +          "email"
        +        ],
        +        "type": "string"
        +      },
        +      "follow_up": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        +            "properties": {
        +              "delay": {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              "to": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "delay",
        +              "to"
        +            ],
        +            "title": "TimedAdvance",
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "send",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "message_template": {
        +        "anyOf": [
        +          {
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "replied": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "channel",
        +      "action",
        +      "replied"
        +    ],
        +    "title": "SendNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
        +    "properties": {
        +      "action": {
        +        "enum": [
        +          "follow",
        +          "withdraw",
        +          "resolve",
        +          "comment",
        +          "reaction"
        +        ],
        +        "type": "string"
        +      },
        +      "channel": {
        +        "const": "linkedin",
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "action",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "notify": {
        +        "default": false,
        +        "type": "boolean"
        +      },
        +      "then": {
        +        "additionalProperties": false,
        +        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
        +        "properties": {
        +          "after": {
        +            "anyOf": [
        +              {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "default": null
        +          },
        +          "to": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "to"
        +        ],
        +        "title": "Advance",
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "channel",
        +      "action",
        +      "then"
        +    ],
        +    "title": "SilentActionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
        +    "properties": {
        +      "action_description": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "manual",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "then": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "action_description",
        +      "then"
        +    ],
        +    "title": "ManualActionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a terminal node's hook — not a field here. Unlike a manual node\nit boots an agent run (rather than waiting on a human) and unlike a terminal hook it\nadvances; structurally it is a decision with one implicit 'always' arm — the run does the\nside-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
        +    "properties": {
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "automation",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "then": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "then"
        +    ],
        +    "title": "AutomationNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
        +    "properties": {
        +      "arms": {
        +        "items": {
        +          "additionalProperties": false,
        +          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
        +          "properties": {
        +            "case": {
        +              "minLength": 1,
        +              "type": "string"
        +            },
        +            "to": {
        +              "type": "string"
        +            }
        +          },
        +          "required": [
        +            "case",
        +            "to"
        +          ],
        +          "title": "Arm",
        +          "type": "object"
        +        },
        +        "minItems": 1,
        +        "type": "array"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "decision",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "rule": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "rule",
        +      "arms"
        +    ],
        +    "title": "DecisionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
        +    "properties": {
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "terminal",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind"
        +    ],
        +    "title": "TerminalNode",
        +    "type": "object"
        +  }
        +]
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the task to attach the sequence to.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "sequence"
        -]New value: +[
        +  "agent_id",
        +  "sequence"
        +]
    • Changeddelete_agent3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the agent to delete",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the task to delete",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id"
        -]New value: +[
        +  "agent_id"
        +]
    • Changededit_monitor_members5 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "The agent the monitor lives on.",
        +  "type": "integer"
        +}
      • changedInput schema / properties / as_teammate / description
        Previous value: -"Edit a consented teammate's monitor instead of your own — pass\ntheir email; task_id must be one of their tasks. Gated on that teammate's\nact-on-behalf setting; a teammate who hasn't granted it is rejected. Omit\nfor your own."New value: +"Edit a consented teammate's monitor instead of your own — pass\ntheir email; agent_id must be one of their agents. Gated on that teammate's\nact-on-behalf setting; a teammate who hasn't granted it is rejected. Omit\nfor your own."
      • changedInput schema / properties / name / description
        Previous value: -"the monitor to edit (must already exist on this task)."New value: +"the monitor to edit (must already exist on this agent)."
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "The task the monitor lives on.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "name"
        -]New value: +[
        +  "agent_id",
        +  "name"
        +]
    • Changedescalate_to_team2 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "The agent this concerns. Defaults to the current agent when in one;\npass it only to point at a different agent."
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "The task this concerns. Defaults to the current task when in one;\npass it only to point at a different task."
        -}
    • Addedestimate_message_tag_classify_scope
    • Addedestimate_segment_classify_scope
    • Changedexa_find_people4 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Optional — a specific agent to save the matched people into.\nOmit it to use the running agent, which is the usual case. Only a\nthrowaway lookup outside any agent returns results without saving."
        +}
      • changedInput schema / properties / list_name / description
        Previous value: -"Short kebab slug naming the Output-tab list bucket (e.g.\n'acme-execs'). When this runs in a task, matched people are saved\nand linked as `agent_search_results` person rows automatically —\ndeduped by profile, re-runs update in place; `list_name` names\ntheir list, and absent it they land in the 'default' list. Query\nthem by reference with `query_search_results` instead of re-typing\nURLs from `results`. The `results` return is unchanged either way."New value: +"Short kebab slug naming the Output-tab list bucket (e.g.\n'acme-execs'). When this runs in an agent, matched people are saved\nand linked as `agent_search_results` person rows automatically —\ndeduped by profile, re-runs update in place; `list_name` names\ntheir list, and absent it they land in the 'default' list. Query\nthem by reference with `query_search_results` instead of re-typing\nURLs from `results`. The `results` return is unchanged either way."
      • changedInput schema / properties / persist / description
        Previous value: -"Default True — in a task, matched people are saved and linked\nas person rows automatically (see `list_name`). Pass False to return\nresults without saving, for a flow that folds these people into\nanother row instead — e.g. a company-find that nests them under each\ncompany row via `record_search_results`, where a standalone person\nlist would duplicate people already shown under their company."New value: +"Default True — in an agent, matched people are saved and linked\nas person rows automatically (see `list_name`). Pass False to return\nresults without saving, for a flow that folds these people into\nanother row instead — e.g. a company-find that nests them under each\ncompany row via `record_search_results`, where a standalone person\nlist would duplicate people already shown under their company."
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Optional — a specific task to save the matched people into.\nOmit it to use the running task, which is the usual case. Only a\nthrowaway lookup outside any task returns results without saving."
        -}
    • Changedextend_find_search3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "The agent hosting the list.",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "The task hosting the list.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "list_name"
        -]New value: +[
        +  "agent_id",
        +  "list_name"
        +]
    • Changedfetch_post_engagers3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Optional — a specific agent to materialize the engager list\ninto. Omit it to use the running agent, which is the usual case."
        +}
      • changedInput schema / properties / persist / description
        Previous value: -"Default True — in a task, every engager is saved as a person\nrow automatically (see `list_name`). Pass False to fetch the\nengagers without saving them as person rows: the raw engagement rows\nstill land and stay queryable via `query_linkedin_post_engagements`,\nbut no `agent_search_results` list is written — for a flow that\nrecords only a filtered subset itself, so the full unfiltered list\nwouldn't also clutter the Output tab."New value: +"Default True — in an agent, every engager is saved as a person\nrow automatically (see `list_name`). Pass False to fetch the\nengagers without saving them as person rows: the raw engagement rows\nstill land and stay queryable via `query_linkedin_post_engagements`,\nbut no `agent_search_results` list is written — for a flow that\nrecords only a filtered subset itself, so the full unfiltered list\nwouldn't also clutter the Output tab."
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Optional — a specific task to materialize the engager list\ninto. Omit it to use the running task, which is the usual case."
        -}
    • Changedget_agent_details3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the agent to load",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the task to load",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id"
        -]New value: +[
        +  "agent_id"
        +]
    • Changedget_campaign_flow3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the agent (campaign) to read.",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the task (campaign) to read.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id"
        -]New value: +[
        +  "agent_id"
        +]
    • Changedget_linkedin_monitors4 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "The agent whose monitors to read.",
        +  "type": "integer"
        +}
      • changedInput schema / properties / monitor_id / description
        Previous value: -"Drill into one monitor's watched-profile roster by its `id`.\nOmit for the per-monitor summary of all monitors on the task."New value: +"Drill into one monitor's watched-profile roster by its `id`.\nOmit for the per-monitor summary of all monitors on the agent."
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "The task whose monitors to read.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id"
        -]New value: +[
        +  "agent_id"
        +]
    • Changedget_message_tag_cross_tab2 fields changed
      • addedInput schema / properties / agent_ids
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "integer"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Scope to one or more campaigns (agent_tasks ids from a group's `campaigns`);\nomit for all campaigns. Campaign membership is the prospect's current one."
        +}
      • removedInput schema / properties / task_ids
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "items": {
        -        "type": "integer"
        -      },
        -      "type": "array"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Scope to one or more campaigns (agent_tasks ids from a group's `campaigns`);\nomit for all campaigns. Campaign membership is the prospect's current one."
        -}
    • Changedget_message_tag_rates3 fields changed
      • addedInput schema / properties / agent_ids
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "integer"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Scope to one or more campaigns (agent_tasks ids from a group's `campaigns`);\nomit for all campaigns. Campaign membership is the prospect's current one."
        +}
      • changedInput schema / properties / position / description
        Previous value: -"'first' (each prospect's opener on a channel) or 'follow_up' (its later sends\non a channel); omit for both."New value: +"'first' (each conversation's first message) or 'follow_up' (later messages\nsent before the prospect replied); omit for both."
      • removedInput schema / properties / task_ids
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "items": {
        -        "type": "integer"
        -      },
        -      "type": "array"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Scope to one or more campaigns (agent_tasks ids from a group's `campaigns`);\nomit for all campaigns. Campaign membership is the prospect's current one."
        -}
    • Addedget_message_tag_unclassified_count
    • Changedget_node_history3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the agent (campaign) to read.",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the task (campaign) to read.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "node_id"
        -]New value: +[
        +  "agent_id",
        +  "node_id"
        +]
    • Changedget_outreach_approval2 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "The agent whose effective settings to read. When omitted, reads the current\nagent's settings if this chat is tied to one, otherwise the account-wide settings."
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "The agent (task) whose effective settings to read. When omitted, reads the current\nagent's settings if this chat is tied to one, otherwise the account-wide settings."
        -}
    • Changedget_run_transcript1 field changed
      • changedInput schema / properties / run_id / description
        Previous value: -"An agent-run id — the `id` of a `get_agent_details` `recent_runs`\nentry (also shown as `(run N)` in the task's Recent Activity feed)."New value: +"An agent-run id — the `id` of a `get_agent_details` `recent_runs`\nentry (also shown as `(run N)` in the agent's Recent Activity feed)."
    • Changedget_segment_rates3 fields changed
      • addedInput schema / properties / agent_ids
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "integer"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Scope to one or more campaigns (agent_tasks ids from a group's `campaigns`); omit\nfor all campaigns."
        +}
      • addedInput schema / properties / senders
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "In a shared workspace, the teammate emails whose prospects to score (a group's\n`senders`). Omit to score every teammate. Any email not in the workspace is dropped; if\nthat leaves no valid teammate, the result is empty — it does NOT fall back to the whole\nworkspace."
        +}
      • removedInput schema / properties / task_ids
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "items": {
        -        "type": "integer"
        -      },
        -      "type": "array"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Scope to one or more campaigns (agent_tasks ids from a group's `campaigns`); omit\nfor all campaigns."
        -}
    • Addedget_segment_unclassified_count
    • Changedget_skill_guide1 field changed
      • changedInput schema / properties / skill / enum
        Previous value: -[
        -  "blog_content_calendar",
        -  "blog_post_writing",
        -  "company_monitor",
        -  "creating_todos",
        -  "email_inbox_management",
        -  "email_sequence_setup",
        -  "exa_find_people",
        -  "find_companies_news_scan",
        -  "find_companies_news_scan_setup",
        -  "find_companies_search_criteria",
        -  "find_companies_webset_mode",
        -  "find_people",
        -  "find_warm_intro_paths",
        -  "linkedin_flowless_campaigns",
        -  "linkedin_outreach",
        -  "linkedin_outreach_debugging",
        -  "linkedin_outreach_finding_leads",
        -  "linkedin_outreach_operations",
        -  "linkedin_outreach_warmup",
        -  "linkedin_post_monitoring",
        -  "linkedin_post_writing",
        -  "message_tagging",
        -  "mixed_channel_outreach",
        -  "news_scan_predictleads",
        -  "news_scan_tavily",
        -  "news_scan_theirstack",
        -  "posthog_setup",
        -  "prospecting_agent",
        -  "scheduling_calendar_events",
        -  "segment_tagging",
        -  "social_listening",
        -  "trigger_code",
        -  "weekly_sales_review"
        -]New value: +[
        +  "blog_content_calendar",
        +  "blog_post_writing",
        +  "company_monitor",
        +  "email_inbox_management",
        +  "email_sequence_setup",
        +  "exa_find_people",
        +  "find_companies_news_scan",
        +  "find_companies_news_scan_setup",
        +  "find_companies_search_criteria",
        +  "find_companies_webset_mode",
        +  "find_people",
        +  "find_warm_intro_paths",
        +  "linkedin_flowless_campaigns",
        +  "linkedin_outreach",
        +  "linkedin_outreach_debugging",
        +  "linkedin_outreach_finding_leads",
        +  "linkedin_outreach_operations",
        +  "linkedin_outreach_warmup",
        +  "linkedin_post_monitoring",
        +  "linkedin_post_writing",
        +  "message_tagging",
        +  "mixed_channel_outreach",
        +  "news_scan_predictleads",
        +  "news_scan_tavily",
        +  "news_scan_theirstack",
        +  "posthog_setup",
        +  "prospecting_agent",
        +  "scheduling_calendar_events",
        +  "segment_tagging",
        +  "social_listening",
        +  "trigger_code",
        +  "weekly_sales_review"
        +]
    • Changedget_social_listening_config3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "The agent whose social-listening config to read.",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "The task whose social-listening config to read.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id"
        -]New value: +[
        +  "agent_id"
        +]
    • Changedlist_agents1 field changed
      • changedInput schema / properties / include_completed / description
        Previous value: -"Set to True to include completed tasks."New value: +"Set to True to include completed agents."
    • Changedlist_attachments2 fields changed
      • addedInput schema / properties / all_agents
        Added value: +{
        +  "default": false,
        +  "description": "list the whole library instead of just the current agent's files.",
        +  "type": "boolean"
        +}
      • removedInput schema / properties / all_tasks
        Removed value: -{
        -  "default": false,
        -  "description": "list the whole library instead of just the current task's files.",
        -  "type": "boolean"
        -}
    • Addedlist_classifiable_campaigns
    • Changedlist_prospect_events2 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Scope the events and the summary to one campaign. Omit for the\ncross-campaign feed. Note `turn` stays prospect-scoped — it can read\n'user' off a draft awaiting approval under a different campaign for\nthe same person; `queued_followup` is campaign-matched and won't."
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Scope the events and the summary to one campaign. Omit for the\ncross-campaign feed. Note `turn` stays prospect-scoped — it can read\n'user' off a draft awaiting approval under a different campaign for\nthe same person; `queued_followup` is campaign-matched and won't."
        -}
    • Changedmanage_email_outreach_queue4 fields changed
      • changedInput schema / properties / action / description
        Previous value: -"One of:\n- 'status': Get full queue details (pending/sent/failed items). The\n  `pending_items` / `pending_approval_items` lists cap at 50 each, each\n  with a `pending_truncated` / `pending_approval_truncated` flag — when a\n  list's flag is True, pass `offset` (offset += 50) to read the next page.\n  Each item carries the `mailbox` it sends from; the account-level\n  `mailboxes` list gives each mailbox's remaining daily headroom. The\n  top-level `cancelled_by_kind` maps each cancel cause to its count\n  (`recipient_replied`, `task_deleted`, `auto_shelved`, …; pre-taxonomy\n  rows under `unknown`) — read it to answer \"how much outreach was\n  cancelled, and why?\".\n- 'update': Edit a queued email's subject and/or body. Requires recipient_email.\n  Pass subject and/or body to change — no cancel-and-re-queue needed. Send order\n  is derived, not per-row scheduled; to pause or resume the campaign use\n  update_agent (see the note above).\n- 'approve': Send authorization belongs to the user, so this action\n  needs a message that arrived after the drafts were queued and\n  explicitly says to approve or send them. A message from before the\n  drafts existed cannot authorize them — a request to review them,\n  to approve them later, or to be able to approve from chat means:\n  report that the queue is awaiting approval and end your turn; the\n  user's next message decides. Once authorized: if recipient_email\n  is given, approve only that item, otherwise approve ALL\n  pending_approval items. From account-level chat an approve-all is\n  rejected unless task_id names the campaign — it must not fire\n  every campaign's drafts into sending at once.\n- 'cancel': Cancel a queued email. Pass recipient_email to cancel that one\n  person. To cancel the ENTIRE pending queue — which drops drafts the user\n  already approved — you must pass\n  cancel_all=True; an unscoped cancel without it is rejected. From\n  account-level chat a cancel_all is rejected unless task_id names the campaign\n  to cancel — it must not clear every campaign's queue at once."New value: +"One of:\n- 'status': Get full queue details (pending/sent/failed items). The\n  `pending_items` / `pending_approval_items` lists cap at 50 each, each\n  with a `pending_truncated` / `pending_approval_truncated` flag — when a\n  list's flag is True, pass `offset` (offset += 50) to read the next page.\n  Each item carries the `mailbox` it sends from; the account-level\n  `mailboxes` list gives each mailbox's remaining daily headroom. The\n  top-level `cancelled_by_kind` maps each cancel cause to its count\n  (`recipient_replied`, `task_deleted`, `auto_shelved`, …; pre-taxonomy\n  rows under `unknown`) — read it to answer \"how much outreach was\n  cancelled, and why?\".\n- 'update': Edit a queued email's subject and/or body. Requires recipient_email.\n  Pass subject and/or body to change — no cancel-and-re-queue needed. Send order\n  is derived, not per-row scheduled; to pause or resume the campaign use\n  update_agent (see the note above).\n- 'approve': Send authorization belongs to the user, so this action\n  needs a message that arrived after the drafts were queued and\n  explicitly says to approve or send them. A message from before the\n  drafts existed cannot authorize them — a request to review them,\n  to approve them later, or to be able to approve from chat means:\n  report that the queue is awaiting approval and end your turn; the\n  user's next message decides. Once authorized: if recipient_email\n  is given, approve only that item, otherwise approve ALL\n  pending_approval items. From account-level chat an approve-all is\n  rejected unless agent_id names the campaign — it must not fire\n  every campaign's drafts into sending at once.\n- 'cancel': Cancel a queued email. Pass recipient_email to cancel that one\n  person. To cancel the ENTIRE pending queue — which drops drafts the user\n  already approved — you must pass\n  cancel_all=True; an unscoped cancel without it is rejected. From\n  account-level chat a cancel_all is rejected unless agent_id names the campaign\n  to cancel — it must not clear every campaign's queue at once."
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "For 'cancel' and 'approve' — scope a bulk cancel_all / approve-all to one\n    campaign's queue. Required from account-level chat (no active agent), where an\n    unscoped bulk cancel or approve is rejected; call list_agents to get the id."
        +}
      • changedInput schema / properties / as_teammate / description
        Previous value: -"Act on a consented teammate's email queue instead of your own —\npass their email. Covers approve / cancel / update / status; a bulk\napprove-all / cancel_all must name one of the teammate's campaigns via\ntask_id. Gated on that teammate's act-on-behalf setting; a teammate who\nhasn't granted it is rejected. Omit for your own."New value: +"Act on a consented teammate's email queue instead of your own —\npass their email. Covers approve / cancel / update / status; a bulk\napprove-all / cancel_all must name one of the teammate's campaigns via\nagent_id. Gated on that teammate's act-on-behalf setting; a teammate who\nhasn't granted it is rejected. Omit for your own."
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "For 'cancel' and 'approve' — scope a bulk cancel_all / approve-all to one\n    campaign's queue. Required from account-level chat (no active task), where an\n    unscoped bulk cancel or approve is rejected; call list_agents to get the id."
        -}
    • Changedmanage_linkedin_invite_queue4 fields changed
      • changedInput schema / properties / action / description
        Previous value: -"One of:\n- 'status': Get full queue details (pending/sent/failed items with names and times).\n  The `pending_items` / `pending_approval_items` / `failed_items` lists cap at 50\n  each, each with a matching `*_truncated` flag — when a list's flag is True, more\n  rows exist past this page; pass `offset` (offset += 50) to read them. Each\n  `failed_items` row carries `last_error`, the reason that send failed — read it to\n  answer \"why wasn't X sent?\". `pending_approval_items` rows also carry `last_error`\n  when the row was held for a reason beyond plain approval gating (e.g. the\n  resolver's name-mismatch hold). Queued post comments and reactions carry `post_url`\n  and a `post_text` excerpt of the post the action targets, so you can tell which\n  post a queued comment is on; `post_text_truncated` True means the target post ran\n  longer than the excerpt shown. All four lists share one field shape; a\n  `resolving_items` row (profile not yet resolved) has a blank `target_provider_id`\n  and its `message` is the copy that will send once it resolves — edit or cancel it\n  by its `target_identifier`. Every `pending_items` / `resolving_items` row carries\n  `projected_send_day` + `projected_send_time` (a resolving row's is its projected\n  lookup time, or its projected send time once it's a resolve-then-send) and a\n  `queue_position` — the row's place in the one ordered lookup+send timeline, so the\n  two lists interleave into what actually fires next. The top-level `cancelled_by_kind` maps each cancel\n  cause to its count (`recipient_replied`, `task_deleted`, `auto_shelved`, …;\n  pre-taxonomy rows under `unknown`) — read it to answer \"how much outreach was\n  cancelled, and why?\".\n- 'update': Update a queued item's message text. Name the item by\n  target_provider_id (a pending row's pid) OR, for a resolving row that has no\n  pid yet, by target_identifier (its URL/slug, shown on the row). Use this to\n  edit a queued message — including on resolving rows, which still hold the\n  message that will send once their profile resolves. When a target has more\n  than one queued action — a post with both a comment and a reaction, or a\n  person with a connection request and a follow — also pass action_type to name\n  which one to edit.\n- 'approve': Send authorization belongs to the user, so this action needs a\n  message that arrived after the drafts were queued and explicitly says to\n  approve or send them. A message from before the drafts existed cannot\n  authorize them — a request to review them, to approve them later, or to be\n  able to approve from chat means: report that the queue is awaiting approval\n  and end your turn; the user's next message decides. Once authorized, this\n  moves pending_approval items to 'pending'; they then send in queue order at\n  your account's pace. If\n  target_provider_id is given, approve only that item. Otherwise approve ALL\n  pending_approval items (optionally filtered by action_type).\n  Only touches items already awaiting approval — it does NOT run pending profile\n  lookups or pre-approve resolving rows (those aren't in the approvals UI, so\n  nobody has reviewed them; they resolve on their own schedule and, if the\n  user's settings gate the action, surface for approval once resolved).\n  From account-level chat an approve-all is rejected unless task_id names the\n  campaign — it must not fire every campaign's drafts into sending at once.\n- 'cancel': Cancel a queued item. Pass target_provider_id to cancel that one\n  person or post — every queued action for them — or target_identifier to cancel\n  a resolving row that has no pid yet. To cancel the ENTIRE pending\n  queue — which drops drafts the user\n  already approved — you must pass\n  cancel_all=True; an unscoped cancel without it is rejected (an action_type\n  or node_id filter alone is still a bulk cancel and also needs cancel_all=True).\n  Narrow a cancel_all to one flow stage with node_id (e.g. restart only the m2\n  messages without touching m3) — the only way to separate two stages that share\n  an action_type. From account-level chat a cancel_all is rejected unless task_id\n  names the campaign to cancel — it must not clear every campaign's queue at once.\n- 'pause': Pause the queue (stops sending, keeps items queued). Pass resume_at\n  to schedule an automatic resume (\"pause while I'm on vacation, resume July 20\");\n  without it the pause is indefinite and only an explicit 'resume' restarts sending.\n- 'resume': Resume the queue (pending items drain in queue order at your account's pace)"New value: +"One of:\n- 'status': Get full queue details (pending/sent/failed items with names and times).\n  The `pending_items` / `pending_approval_items` / `failed_items` lists cap at 50\n  each, each with a matching `*_truncated` flag — when a list's flag is True, more\n  rows exist past this page; pass `offset` (offset += 50) to read them. Each\n  `failed_items` row carries `last_error`, the reason that send failed — read it to\n  answer \"why wasn't X sent?\". `pending_approval_items` rows also carry `last_error`\n  when the row was held for a reason beyond plain approval gating (e.g. the\n  resolver's name-mismatch hold). Queued post comments and reactions carry `post_url`\n  and a `post_text` excerpt of the post the action targets, so you can tell which\n  post a queued comment is on; `post_text_truncated` True means the target post ran\n  longer than the excerpt shown. All four lists share one field shape; a\n  `resolving_items` row (profile not yet resolved) has a blank `target_provider_id`\n  and its `message` is the copy that will send once it resolves — edit or cancel it\n  by its `target_identifier`. Every `pending_items` / `resolving_items` row carries\n  `projected_send_day` + `projected_send_time` (a resolving row's is its projected\n  lookup time, or its projected send time once it's a resolve-then-send) and a\n  `queue_position` — the row's place in the one ordered lookup+send timeline, so the\n  two lists interleave into what actually fires next. The top-level `cancelled_by_kind` maps each cancel\n  cause to its count (`recipient_replied`, `task_deleted`, `auto_shelved`, …;\n  pre-taxonomy rows under `unknown`) — read it to answer \"how much outreach was\n  cancelled, and why?\".\n- 'update': Update a queued item's message text. Name the item by\n  target_provider_id (a pending row's pid) OR, for a resolving row that has no\n  pid yet, by target_identifier (its URL/slug, shown on the row). Use this to\n  edit a queued message — including on resolving rows, which still hold the\n  message that will send once their profile resolves. When a target has more\n  than one queued action — a post with both a comment and a reaction, or a\n  person with a connection request and a follow — also pass action_type to name\n  which one to edit.\n- 'approve': Send authorization belongs to the user, so this action needs a\n  message that arrived after the drafts were queued and explicitly says to\n  approve or send them. A message from before the drafts existed cannot\n  authorize them — a request to review them, to approve them later, or to be\n  able to approve from chat means: report that the queue is awaiting approval\n  and end your turn; the user's next message decides. Once authorized, this\n  moves pending_approval items to 'pending'; they then send in queue order at\n  your account's pace. If\n  target_provider_id is given, approve only that item. Otherwise approve ALL\n  pending_approval items (optionally filtered by action_type).\n  Only touches items already awaiting approval — it does NOT run pending profile\n  lookups or pre-approve resolving rows (those aren't in the approvals UI, so\n  nobody has reviewed them; they resolve on their own schedule and, if the\n  user's settings gate the action, surface for approval once resolved).\n  From account-level chat an approve-all is rejected unless agent_id names the\n  campaign — it must not fire every campaign's drafts into sending at once.\n- 'cancel': Cancel a queued item. Pass target_provider_id to cancel that one\n  person or post — every queued action for them — or target_identifier to cancel\n  a resolving row that has no pid yet. To cancel the ENTIRE pending\n  queue — which drops drafts the user\n  already approved — you must pass\n  cancel_all=True; an unscoped cancel without it is rejected (an action_type\n  or node_id filter alone is still a bulk cancel and also needs cancel_all=True).\n  Narrow a cancel_all to one flow stage with node_id (e.g. restart only the m2\n  messages without touching m3) — the only way to separate two stages that share\n  an action_type. From account-level chat a cancel_all is rejected unless agent_id\n  names the campaign to cancel — it must not clear every campaign's queue at once.\n- 'pause': Pause the queue (stops sending, keeps items queued). Pass resume_at\n  to schedule an automatic resume (\"pause while I'm on vacation, resume July 20\");\n  without it the pause is indefinite and only an explicit 'resume' restarts sending.\n- 'resume': Resume the queue (pending items drain in queue order at your account's pace)"
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "For 'cancel' and 'approve' — scope a bulk cancel_all / approve-all to one\n    campaign's queue. Required from account-level chat (no active agent), where an\n    unscoped bulk cancel or approve is rejected; call list_agents to get the id."
        +}
      • changedInput schema / properties / as_teammate / description
        Previous value: -"Act on a consented teammate's queue instead of your own — pass\ntheir email. Covers approve / cancel / update / status; pause and resume\n(account-wide controls) are not available on a teammate's behalf. A bulk\napprove-all / cancel_all must name one of the teammate's campaigns via\ntask_id. Gated on that teammate's act-on-behalf setting; a teammate who\nhasn't granted it is rejected. Omit for your own."New value: +"Act on a consented teammate's queue instead of your own — pass\ntheir email. Covers approve / cancel / update / status; pause and resume\n(account-wide controls) are not available on a teammate's behalf. A bulk\napprove-all / cancel_all must name one of the teammate's campaigns via\nagent_id. Gated on that teammate's act-on-behalf setting; a teammate who\nhasn't granted it is rejected. Omit for your own."
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "For 'cancel' and 'approve' — scope a bulk cancel_all / approve-all to one\n    campaign's queue. Required from account-level chat (no active task), where an\n    unscoped bulk cancel or approve is rejected; call list_agents to get the id."
        -}
    • Changedmove_prospect_to_node4 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the agent whose flow the prospect is on.",
        +  "type": "integer"
        +}
      • changedInput schema / properties / node_id / description
        Previous value: -"A connection_request / send / action / manual / automation / decision /\nterminal node id in the task's sequence (not start)."New value: +"A connection_request / send / action / manual / automation / decision /\nterminal node id in the agent's sequence (not start)."
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the task whose flow the prospect is on.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "prospect_id",
        -  "node_id"
        -]New value: +[
        +  "agent_id",
        +  "prospect_id",
        +  "node_id"
        +]
    • Changedquery_analytics1 field changed
      • changedInput schema / properties / metric / description
        Previous value: -"One of \"traffic\", \"sources\", \"top_pages\", \"top_clicks\", \"engagement\", \"daily_digest\", \"digest_view\", \"custom\". Use \"digest_view\" to read exactly what the user sees on this task's Analytics Output tab — the derived today + history entries (each with week-over-week deltas) + PostHog link; the other metrics return raw snapshot data."New value: +"One of \"traffic\", \"sources\", \"top_pages\", \"top_clicks\", \"engagement\", \"daily_digest\", \"digest_view\", \"custom\". Use \"digest_view\" to read exactly what the user sees on this agent's Analytics Output tab — the derived today + history entries (each with week-over-week deltas) + PostHog link; the other metrics return raw snapshot data."
    • Changedquery_monitored_companies2 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Filter to a specific agent. Omit for cross-agent queries."
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Filter to a specific task. Omit for cross-task queries."
        -}
    • Changedquery_monitored_posts2 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Scope to one agent's monitors. Omit for cross-agent queries."
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Scope to one task's monitors. Omit for cross-task queries."
        -}
    • Changedquery_people1 field changed
      • changedInput schema / properties / group_by / description
        Previous value: -"Aggregate mode, returned instead of the row list — whole-set per-bucket counts\nover all your people (not capped by `limit`). \"title\" / \"company\" / \"location\" bucket\nby that firmographic; \"outreach_stage\" by the person's collapsed lead-funnel bucket;\n\"segment\" by the person's tag in the `segment_group_id` group. Omit for the row list."New value: +"Aggregate mode, returned instead of the row list — whole-set per-bucket counts\nover all your people (not capped by `limit`). \"title\" / \"company\" / \"location\" bucket\nby that firmographic — \"title\" falls back to the headline when the title is blank, so\nlist a title bucket's people with \"COALESCE(NULLIF(title, ''), headline) = '<key>'\";\n\"outreach_stage\" by the person's collapsed lead-funnel bucket;\n\"segment\" by the person's tag in the `segment_group_id` group. Omit for the row list."
    • Changedquery_prospect_research4 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "The agent whose saved research to read.",
        +  "type": "integer"
        +}
      • changedInput schema / properties / person / description
        Previous value: -"A lead handle (linkedin_url / linkedin_provider_id / email) to\nget just that lead's findings; omit for all research on the task."New value: +"A lead handle (linkedin_url / linkedin_provider_id / email) to\nget just that lead's findings; omit for all research on the agent."
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "The task whose saved research to read.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id"
        -]New value: +[
        +  "agent_id"
        +]
    • Changedquery_prospects3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Filter to a specific agent. Omit for cross-agent queries."
        +}
      • changedInput schema / properties / as_teammate / description
        Previous value: -"Read a consented teammate's prospects instead of your own —\npass their email. Gated on that teammate's conversation-sharing\nsetting; a teammate who hasn't shared is rejected. Any `task_id` must\nbe one of that teammate's tasks. Omit for your own."New value: +"Read a consented teammate's prospects instead of your own —\npass their email. Gated on that teammate's conversation-sharing\nsetting; a teammate who hasn't shared is rejected. Any `agent_id` must\nbe one of that teammate's agents. Omit for your own."
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Filter to a specific task. Omit for cross-task queries."
        -}
    • Changedquery_search_results6 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "Filter to a specific agent (required).",
        +  "type": "integer"
        +}
      • changedInput schema / properties / group_by / anyOf
        Previous value: -[
        -  {
        -    "enum": [
        -      "list",
        -      "company"
        -    ],
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "enum": [
        +      "list",
        +      "company",
        +      "source"
        +    ],
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / group_by / description
        Previous value: -"Aggregate mode, returned instead of the row list — per-bucket counts\nover the whole task (not truncated by the row cap). \"list\" buckets rows by\nlist_name; \"company\" buckets people by employer. Omit for the row list."New value: +"Aggregate mode, returned instead of the row list — per-bucket counts\nover the whole agent (not truncated by the row cap). \"list\" buckets rows by\nlist_name; \"company\" buckets people by employer; \"source\" buckets rows by\nthe discovery tool that surfaced them. Omit for the row list."
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "Filter to a specific task (required).",
        -  "type": "integer"
        -}
      • changedInput schema / properties / where_clause / description
        Previous value: -"SQL WHERE condition (default returns all rows for the task).\nExamples: \"data->'person'->>'position' ILIKE '%VP%'\",\n\"entity_type = 'person'\", \"list_name = 'oil-gas-operators'\"."New value: +"SQL WHERE condition (default returns all rows for the agent).\nExamples: \"data->'person'->>'position' ILIKE '%VP%'\",\n\"entity_type = 'person'\", \"list_name = 'oil-gas-operators'\"."
      • changedInput schema / required
        Previous value: -[
        -  "task_id"
        -]New value: +[
        +  "agent_id"
        +]
    • Changedquery_segment_people4 fields changed
      • addedInput schema / properties / agent_ids
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "integer"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Scope to people with a prospect in one or more campaigns (agent_tasks ids); omit\nfor all."
        +}
      • addedInput schema / properties / senders
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Narrow to people reached by these teammates (emails from a group's `senders`), and\nread their outcome from those teammates' prospects only. Omit for every teammate; an\nemail not in the workspace is dropped, and if none remain the result is empty."
        +}
      • removedInput schema / properties / task_ids
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "items": {
        -        "type": "integer"
        -      },
        -      "type": "array"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Scope to people with a prospect in one or more campaigns (agent_tasks ids); omit\nfor all."
        -}
      • addedInput schema / properties / untagged
        Added value: +{
        +  "default": false,
        +  "description": "Narrow to the 'No match' people (classified as nothing, empty `tags`); takes\nprecedence over `tag_id`. Omit for all classified people.",
        +  "type": "boolean"
        +}
    • Changedquery_tagged_messages4 fields changed
      • addedInput schema / properties / agent_ids
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "integer"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Scope to one or more campaigns (agent_tasks ids from a group's `campaigns`);\nomit for all campaigns. Campaign membership is the prospect's current one."
        +}
      • changedInput schema / properties / position / description
        Previous value: -"'first' (each prospect's opener on a channel) or 'follow_up' (its later sends\non a channel); omit for both."New value: +"'first' (each conversation's first message) or 'follow_up' (later messages\nsent before the prospect replied); omit for both."
      • removedInput schema / properties / task_ids
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "items": {
        -        "type": "integer"
        -      },
        -      "type": "array"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Scope to one or more campaigns (agent_tasks ids from a group's `campaigns`);\nomit for all campaigns. Campaign membership is the prospect's current one."
        -}
      • addedInput schema / properties / untagged
        Added value: +{
        +  "default": false,
        +  "description": "Narrow to the 'No match' messages (classified as nothing, empty `tags`); takes\nprecedence over `tag_id`. Omit for all classified messages.",
        +  "type": "boolean"
        +}
    • Changedqueue_linkedin_post_engagement1 field changed
      • changedInput schema / properties / as_teammate / description
        Previous value: -"Queue the engagement on a consented teammate's account instead\nof your own — pass their email. It lands as a standalone (account-level)\nengagement on their queue, not tied to any of your tasks. Gated on that\nteammate's act-on-behalf setting; a teammate who hasn't granted it is\nrejected. Omit for your own."New value: +"Queue the engagement on a consented teammate's account instead\nof your own — pass their email. It lands as a standalone (account-level)\nengagement on their queue, not tied to any of your agents. Gated on that\nteammate's act-on-behalf setting; a teammate who hasn't granted it is\nrejected. Omit for your own."
    • Changedrecord_search_results7 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "The agent these rows belong to.",
        +  "type": "integer"
        +}
      • changedInput schema / properties / results / description
        Previous value: -"The people/company rows to upsert; each SearchResult carries its\nidentifier, display_name, and any signals/columns to store."New value: +"The people/company rows to upsert; each SearchResult carries its\nidentifier, display_name, source, and any signals/columns to store."
      • changedInput schema / properties / results / items / properties / data / properties / network_distance / anyOf
        Previous value: -[
        -  {
        -    "type": "integer"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "description": "A person's LinkedIn degree to the user, closest first.",
        +    "enum": [
        +      "1",
        +      "2",
        +      "3",
        +      "out_of_network"
        +    ],
        +    "title": "Degree",
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / results / items / properties / identifier / description
        Previous value: -"Company domain or canonical URL — unique within the task."New value: +"Company domain or canonical URL — unique within the agent."
      • addedInput schema / properties / results / items / properties / source
        Added value: +{
        +  "default": "agent",
        +  "description": "Where this row came from: the discovery tool that surfaced this candidate — 'theirstack' (theirstack_search, find_companies_by_tech_stack), 'tavily' (tavily_search), 'predictleads' (search_news_events), 'exa_news' (exa_search_news), 'google_news' (google_news_search), 'apollo' (apollo_search), 'exa_find_people', 'search_linkedin_people', 'post_engagers' (fetch_post_engagers, query_linkedin_post_engagements), 'luma_guests' (get_luma_guests). Use 'agent' for a row no discovery tool surfaced, such as a pasted list or a CRM read. Recorded only when the row is first created; an existing row keeps its original source.",
        +  "enum": [
        +    "agent",
        +    "exa_find_people",
        +    "search_linkedin_people",
        +    "theirstack",
        +    "luma_guests",
        +    "post_engagers",
        +    "exa_websets",
        +    "warm_intro",
        +    "tavily",
        +    "predictleads",
        +    "exa_news",
        +    "google_news",
        +    "apollo"
        +  ],
        +  "type": "string"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "The task these rows belong to.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "results",
        -  "list_name"
        -]New value: +[
        +  "agent_id",
        +  "results",
        +  "list_name"
        +]
    • Changedregister_manual_linkedin_invites5 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "The agent whose tracked prospects were manually invited.",
        +  "type": "integer"
        +}
      • changedInput schema / properties / all_tracked / description
        Previous value: -"Register every tracked prospect in the task — use only when the user\ninvited the whole campaign by hand."New value: +"Register every tracked prospect in the agent — use only when the user\ninvited the whole campaign by hand."
      • changedInput schema / properties / node_id / description
        Previous value: -"Optional. The sequence-DAG connection_request node the hand-sent CR\nsatisfies (mirrors setup_linkedin_sequence's node_id) — stamped on the recorded\nrow so its acceptance advances the flow onto the next node. Omit to auto-detect\nthe task's sole CR node; pass it explicitly on a multi-CR sequence. Rejected with\nModelRetry if it isn't a connection_request node in the task's sequence."New value: +"Optional. The sequence-DAG connection_request node the hand-sent CR\nsatisfies (mirrors setup_linkedin_sequence's node_id) — stamped on the recorded\nrow so its acceptance advances the flow onto the next node. Omit to auto-detect\nthe agent's sole CR node; pass it explicitly on a multi-CR sequence. Rejected with\nModelRetry if it isn't a connection_request node in the agent's sequence."
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "The task whose tracked prospects were manually invited.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id"
        -]New value: +[
        +  "agent_id"
        +]
    • Changedremove_trigger1 field changed
      • changedInput schema / properties / trigger_id / description
        Previous value: -"ID of the trigger to remove (from the task's trigger list)."New value: +"ID of the trigger to remove (from the agent's trigger list)."
    • Changedreport_enactment_blocked3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the agent whose flow the prospect is on.",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the task whose flow the prospect is on.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "prospect_id",
        -  "node_id",
        -  "reason"
        -]New value: +[
        +  "agent_id",
        +  "prospect_id",
        +  "node_id",
        +  "reason"
        +]
    • Changedrequest_user_action3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the campaign agent this request belongs to.",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the campaign task this request belongs to.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "prospect_id",
        -  "bucket_name",
        -  "action_description"
        -]New value: +[
        +  "agent_id",
        +  "prospect_id",
        +  "bucket_name",
        +  "action_description"
        +]
    • Changedretry_blocked_enactment3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the agent whose flow the prospect is on.",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the task whose flow the prospect is on.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "prospect_id"
        -]New value: +[
        +  "agent_id",
        +  "prospect_id"
        +]
    • Changedsave_prospect_research3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "The agent these findings belong to.",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "The task these findings belong to.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "findings"
        -]New value: +[
        +  "agent_id",
        +  "findings"
        +]
    • Changedsearch_linkedin_people1 field changed
      • changedInput schema / properties / list_name / description
        Previous value: -"Optional short slug naming the Output-tab list the matches are\n      saved under when this runs in a task. Absent, they save to the\n      'default' list. Reuse the same slug across a loop or follow-up\n      searches to gather them into one list (deduped by profile)."New value: +"Optional short slug naming the Output-tab list the matches are\n      saved under when this runs in an agent. Absent, they save to the\n      'default' list. Reuse the same slug across a loop or follow-up\n      searches to gather them into one list (deduped by profile)."
    • Changedsearch_monitor_events2 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Filter by specific monitor agent. Omit to search across all your\nagents (inside an agent run, an omitted agent_id defaults to that run's agent)."
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Filter by specific monitor task. Omit to search across all your\ntasks (inside a task run, an omitted task_id defaults to that run's task)."
        -}
    • Changedsearch_social_posts2 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Filter by specific social listening agent. Omit to search across\nall your agents (inside an agent run, an omitted agent_id defaults to that\nrun's agent)."
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Filter by specific social listening task. Omit to search across\nall your tasks (inside a task run, an omitted task_id defaults to that\nrun's task)."
        -}
    • Removedsearch_todos
    • Changedset_outreach_approval2 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "The agent to change. Defaults to the agent in context."
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "The agent/task to change. Defaults to the task in context."
        -}
    • Changedsetup_email_sequence5 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "Required. Agent ID to associate with the queued items.",
        +  "type": "integer"
        +}
      • changedInput schema / properties / is_follow_up / description
        Previous value: -"If True, send as a threaded reply to the original outreach\nemail instead of a new email. Requires recipients to have been\npreviously emailed via this task."New value: +"If True, send as a threaded reply to the original outreach\nemail instead of a new email. Requires recipients to have been\npreviously emailed via this agent."
      • changedInput schema / properties / node_id / description
        Previous value: -"Optional. The id of the sequence-DAG node this batch enacts\n(a node in the task's `sequence`, authored via `define_sequence`).\nStamped on every queued row so the Campaign Flow view can place\neach prospect and count per node. Pass it whenever the task has a\nsequence; omit for flowless/ad-hoc sends. Rejected with ModelRetry\nif it isn't a node in the task's sequence."New value: +"Optional. The id of the sequence-DAG node this batch enacts\n(a node in the agent's `sequence`, authored via `define_sequence`).\nStamped on every queued row so the Campaign Flow view can place\neach prospect and count per node. Pass it whenever the agent has a\nsequence; omit for flowless/ad-hoc sends. Rejected with ModelRetry\nif it isn't a node in the agent's sequence."
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "Required. Task ID to associate with the queued items.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "recipients",
        -  "task_id"
        -]New value: +[
        +  "recipients",
        +  "agent_id"
        +]
    • Changedsetup_linkedin_monitoring5 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "The agent this monitor lives on.",
        +  "type": "integer"
        +}
      • changedInput schema / properties / engager_instructions / description
        Previous value: -"optional — what to do with captured engagers. Blank\nmeans collect them into the task's Output list and take no action."New value: +"optional — what to do with captured engagers. Blank\nmeans collect them into the agent's Output list and take no action."
      • changedInput schema / properties / fetch_engagers / description
        Previous value: -"capture each post's engagers and wake this task when\nnew people engage."New value: +"capture each post's engagers and wake this agent when\nnew people engage."
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "The task this monitor lives on.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "name",
        -  "mode"
        -]New value: +[
        +  "agent_id",
        +  "name",
        +  "mode"
        +]
    • Changedsetup_linkedin_sequence3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Optional. Pass for campaigns or when inside an existing agent.\nOmit for ad hoc sends — the tool auto-uses the \"LinkedIn Quick\nActions\" default agent. Every send lands in the agent you name;\nrecipients already tracked in a different one are reported in\n`tracking_skipped` rather than queued (see Returns)."
        +}
      • changedInput schema / properties / node_id / description
        Previous value: -"Optional. The id of the sequence-DAG node this batch enacts\n(a node in the task's `sequence`, authored via `define_sequence`).\nStamped on every queued row so the Campaign Flow view can place\neach prospect and count per node. Pass it whenever the task has a\nsequence; omit for flowless/ad-hoc sends. Rejected with ModelRetry\nif it isn't a node in the task's sequence."New value: +"Optional. The id of the sequence-DAG node this batch enacts\n(a node in the agent's `sequence`, authored via `define_sequence`).\nStamped on every queued row so the Campaign Flow view can place\neach prospect and count per node. Pass it whenever the agent has a\nsequence; omit for flowless/ad-hoc sends. Rejected with ModelRetry\nif it isn't a node in the agent's sequence."
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Optional. Pass for campaigns or when inside an existing task.\nOmit for ad hoc sends — the tool auto-uses the \"LinkedIn Quick\nActions\" default task. Every send lands in the task you name;\nrecipients already tracked in a different one are reported in\n`tracking_skipped` rather than queued (see Returns)."
        -}
    • Changedsetup_social_listening3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "The agent this config lives on.",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "The task this config lives on.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id"
        -]New value: +[
        +  "agent_id"
        +]
    • Changedstart_find_search5 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "The agent to host the new list.",
        +  "type": "integer"
        +}
      • changedInput schema / properties / entity_type / description
        Previous value: -"'person' or 'company'. Mixed people+company tasks are\nsupported — each list has its own row type."New value: +"'person' or 'company'. Mixed people+company agents are\nsupported — each list has its own row type."
      • changedInput schema / properties / list_name / description
        Previous value: -"short kebab slug identifying the bucket (e.g.\n'oil-gas-operators'). Pick a name distinct from any existing list\non the task. Re-using a finished list's name extends it; re-using\na running list's name is refused."New value: +"short kebab slug identifying the bucket (e.g.\n'oil-gas-operators'). Pick a name distinct from any existing list\non the agent. Re-using a finished list's name extends it; re-using\na running list's name is refused."
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "The task to host the new list.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "list_name",
        -  "criteria",
        -  "entity_type"
        -]New value: +[
        +  "agent_id",
        +  "list_name",
        +  "criteria",
        +  "entity_type"
        +]
    • Changedstop_linkedin_monitoring3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "The agent the monitor lives on.",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "The task the monitor lives on.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "name"
        -]New value: +[
        +  "agent_id",
        +  "name"
        +]
    • Changedtheirstack_search3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Optional — a specific agent to save the companies into. Omit\nit to use the running agent, which is the usual case. Only a\nthrowaway lookup outside any agent returns postings without saving."
        +}
      • changedInput schema / properties / persist / description
        Previous value: -"Default True — in a task, the companies are saved as company\nrows automatically (see above). Pass False to return the postings\nwithout saving, for a read-only query or a flow that records a\nvalidated/filtered subset itself."New value: +"Default True — in an agent, the companies are saved as company\nrows automatically (see above). Pass False to return the postings\nwithout saving, for a read-only query or a flow that records a\nvalidated/filtered subset itself."
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Optional — a specific task to save the companies into. Omit\nit to use the running task, which is the usual case. Only a\nthrowaway lookup outside any task returns postings without saving."
        -}
    • Changedtrack_monitored_companies3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the agent to add companies to",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the task to add companies to",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "items"
        -]New value: +[
        +  "agent_id",
        +  "items"
        +]
    • Changedtrack_prospects3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the agent to add prospects to",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the task to add prospects to",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "items"
        -]New value: +[
        +  "agent_id",
        +  "items"
        +]
    • Changedupdate_agent4 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the agent to update",
        +  "type": "integer"
        +}
      • changedInput schema / properties / status / description
        Previous value: -"Set to 'active' or 'paused'. Pausing stops the whole task — every trigger on it."New value: +"Set to 'active' or 'paused'. Pausing stops the whole agent — every trigger on it."
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the task to update",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id"
        -]New value: +[
        +  "agent_id"
        +]
    • Changedupdate_message_tag_group1 field changed
      • addedInput schema / properties / criteria
        Added value: +{
        +  "anyOf": [
        +    {
        +      "description": "The persisted classify scope a question tracks. Each axis null = all (every channel /\nevery campaign / first messages and follow-ups). Omit the whole object to leave criteria unset\n(create) or unchanged (edit).",
        +      "properties": {
        +        "agent_ids": {
        +          "anyOf": [
        +            {
        +              "items": {
        +                "type": "integer"
        +              },
        +              "type": "array"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ],
        +          "default": null
        +        },
        +        "channel": {
        +          "anyOf": [
        +            {
        +              "description": "The outreach channel a query is bounded to; None means both.",
        +              "enum": [
        +                "linkedin",
        +                "email"
        +              ],
        +              "title": "Channel",
        +              "type": "string"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ],
        +          "default": null
        +        },
        +        "position": {
        +          "anyOf": [
        +            {
        +              "enum": [
        +                "first",
        +                "follow_up"
        +              ],
        +              "type": "string"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ],
        +          "default": null
        +        }
        +      },
        +      "title": "CriteriaInput",
        +      "type": "object"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "The classify scope to track — {channel, agent_ids, position}, each null = all\n(see create_message_tag_group). A provided object REPLACES every axis; omit to leave\nthe stored criteria unchanged. classify reuses this scope, so change it to re-target\nwhat the question tracks."
        +}
    • Changedupdate_monitored_company3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the agent",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the task",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "identifier"
        -]New value: +[
        +  "agent_id",
        +  "identifier"
        +]
    • Changedupdate_node5 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the agent whose flow holds the node.",
        +  "type": "integer"
        +}
      • changedInput schema / properties / node / description
        Previous value: -"The node's complete new definition — an omitted optional slot (a `follow_up`, a\n`timeout`) is dropped, not preserved, so carry forward every field the node keeps.\nIts `id` must name an existing body node and its `kind` must match that node's\ncurrent kind."New value: +"The node's complete new definition — an omitted optional slot (a `follow_up`, a\n`timeout`) is dropped, not preserved, so carry forward every field the node keeps.\nA send node's `message_template` is the exception: omitted leaves its template as it is.\nIts `id` must name an existing body node and its `kind` must match that node's\ncurrent kind."
      • changedInput schema / properties / node / oneOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
        -    "properties": {
        -      "accepted": {
        -        "type": "string"
        -      },
        -      "already_connected": {
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "connection_request",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "timeout": {
        -        "anyOf": [
        -          {
        -            "additionalProperties": false,
        -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        -            "properties": {
        -              "delay": {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              "to": {
        -                "type": "string"
        -              }
        -            },
        -            "required": [
        -              "delay",
        -              "to"
        -            ],
        -            "title": "TimedAdvance",
        -            "type": "object"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "accepted",
        -      "already_connected"
        -    ],
        -    "title": "ConnectionRequestNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).",
        -    "properties": {
        -      "action": {
        -        "enum": [
        -          "message",
        -          "inmail",
        -          "email"
        -        ],
        -        "type": "string"
        -      },
        -      "channel": {
        -        "enum": [
        -          "linkedin",
        -          "email"
        -        ],
        -        "type": "string"
        -      },
        -      "follow_up": {
        -        "anyOf": [
        -          {
        -            "additionalProperties": false,
        -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        -            "properties": {
        -              "delay": {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              "to": {
        -                "type": "string"
        -              }
        -            },
        -            "required": [
        -              "delay",
        -              "to"
        -            ],
        -            "title": "TimedAdvance",
        -            "type": "object"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "send",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "replied": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "channel",
        -      "action",
        -      "replied"
        -    ],
        -    "title": "SendNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
        -    "properties": {
        -      "action": {
        -        "enum": [
        -          "follow",
        -          "withdraw",
        -          "resolve",
        -          "comment",
        -          "reaction"
        -        ],
        -        "type": "string"
        -      },
        -      "channel": {
        -        "const": "linkedin",
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "action",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "notify": {
        -        "default": false,
        -        "type": "boolean"
        -      },
        -      "then": {
        -        "additionalProperties": false,
        -        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
        -        "properties": {
        -          "after": {
        -            "anyOf": [
        -              {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "cd",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              {
        -                "type": "null"
        -              }
        -            ],
        -            "default": null
        -          },
        -          "to": {
        -            "type": "string"
        -          }
        -        },
        -        "required": [
        -          "to"
        -        ],
        -        "title": "Advance",
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "channel",
        -      "action",
        -      "then"
        -    ],
        -    "title": "SilentActionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
        -    "properties": {
        -      "action_description": {
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "manual",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "then": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "action_description",
        -      "then"
        -    ],
        -    "title": "ManualActionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a send node's template and a terminal node's hook — not a field\nhere. Unlike a manual node it boots an agent run (rather than waiting on a human) and unlike\na terminal hook it advances; structurally it is a decision with one implicit 'always' arm —\nthe run does the side-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
        -    "properties": {
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "automation",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "then": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "then"
        -    ],
        -    "title": "AutomationNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
        -    "properties": {
        -      "arms": {
        -        "items": {
        -          "additionalProperties": false,
        -          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
        -          "properties": {
        -            "case": {
        -              "minLength": 1,
        -              "type": "string"
        -            },
        -            "to": {
        -              "type": "string"
        -            }
        -          },
        -          "required": [
        -            "case",
        -            "to"
        -          ],
        -          "title": "Arm",
        -          "type": "object"
        -        },
        -        "minItems": 1,
        -        "type": "array"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "decision",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "rule": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "rule",
        -      "arms"
        -    ],
        -    "title": "DecisionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
        -    "properties": {
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "terminal",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind"
        -    ],
        -    "title": "TerminalNode",
        -    "type": "object"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
        +    "properties": {
        +      "accepted": {
        +        "type": "string"
        +      },
        +      "already_connected": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "connection_request",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "timeout": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        +            "properties": {
        +              "delay": {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              "to": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "delay",
        +              "to"
        +            ],
        +            "title": "TimedAdvance",
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "accepted",
        +      "already_connected"
        +    ],
        +    "title": "ConnectionRequestNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).\n\n`message_template` is input-only: excluded from every dump, so it never lands in the stored\nsequence — the step's template lives in its own rows and is merged back in on read.",
        +    "properties": {
        +      "action": {
        +        "enum": [
        +          "message",
        +          "inmail",
        +          "email"
        +        ],
        +        "type": "string"
        +      },
        +      "channel": {
        +        "enum": [
        +          "linkedin",
        +          "email"
        +        ],
        +        "type": "string"
        +      },
        +      "follow_up": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        +            "properties": {
        +              "delay": {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              "to": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "delay",
        +              "to"
        +            ],
        +            "title": "TimedAdvance",
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "send",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "message_template": {
        +        "anyOf": [
        +          {
        +            "type": "string"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "replied": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "channel",
        +      "action",
        +      "replied"
        +    ],
        +    "title": "SendNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
        +    "properties": {
        +      "action": {
        +        "enum": [
        +          "follow",
        +          "withdraw",
        +          "resolve",
        +          "comment",
        +          "reaction"
        +        ],
        +        "type": "string"
        +      },
        +      "channel": {
        +        "const": "linkedin",
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "action",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "notify": {
        +        "default": false,
        +        "type": "boolean"
        +      },
        +      "then": {
        +        "additionalProperties": false,
        +        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
        +        "properties": {
        +          "after": {
        +            "anyOf": [
        +              {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "default": null
        +          },
        +          "to": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "to"
        +        ],
        +        "title": "Advance",
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "channel",
        +      "action",
        +      "then"
        +    ],
        +    "title": "SilentActionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
        +    "properties": {
        +      "action_description": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "manual",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "then": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "action_description",
        +      "then"
        +    ],
        +    "title": "ManualActionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a terminal node's hook — not a field here. Unlike a manual node\nit boots an agent run (rather than waiting on a human) and unlike a terminal hook it\nadvances; structurally it is a decision with one implicit 'always' arm — the run does the\nside-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
        +    "properties": {
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "automation",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "then": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "then"
        +    ],
        +    "title": "AutomationNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
        +    "properties": {
        +      "arms": {
        +        "items": {
        +          "additionalProperties": false,
        +          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
        +          "properties": {
        +            "case": {
        +              "minLength": 1,
        +              "type": "string"
        +            },
        +            "to": {
        +              "type": "string"
        +            }
        +          },
        +          "required": [
        +            "case",
        +            "to"
        +          ],
        +          "title": "Arm",
        +          "type": "object"
        +        },
        +        "minItems": 1,
        +        "type": "array"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "decision",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "rule": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "rule",
        +      "arms"
        +    ],
        +    "title": "DecisionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
        +    "properties": {
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "terminal",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind"
        +    ],
        +    "title": "TerminalNode",
        +    "type": "object"
        +  }
        +]
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the task whose flow holds the node.",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "node"
        -]New value: +[
        +  "agent_id",
        +  "node"
        +]
    • Changedupdate_prospect3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the agent",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the task",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "person",
        -  "channel"
        -]New value: +[
        +  "agent_id",
        +  "person",
        +  "channel"
        +]
    • Changedupdate_segment_group1 field changed
      • addedInput schema / properties / criteria
        Added value: +{
        +  "anyOf": [
        +    {
        +      "description": "The persisted classify scope a segment tracks — campaigns only (a person has no intrinsic\nchannel, so channel is a view filter, never a classify criterion). `agent_ids` null = every\ncampaign. Omit the whole object to leave criteria unset (create) or unchanged (edit).",
        +      "properties": {
        +        "agent_ids": {
        +          "anyOf": [
        +            {
        +              "items": {
        +                "type": "integer"
        +              },
        +              "type": "array"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ],
        +          "default": null
        +        }
        +      },
        +      "title": "CriteriaInput",
        +      "type": "object"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "The classify scope to track — {agent_ids}, the campaign (agent_tasks) ids (null =\nevery campaign). A provided object REPLACES the stored scope; omit to leave it unchanged.\nclassify reuses this scope, so change it to re-target what the segment tracks."
        +}
    • Removedupdate_todo
    • Changedupdate_trigger1 field changed
      • changedInput schema / properties / trigger_id / description
        Previous value: -"ID of the trigger to edit (from the task's trigger list)."New value: +"ID of the trigger to edit (from the agent's trigger list)."
    • Changedupdate_workspace3 fields changed
      • addedInput schema / properties / agent_id
        Added value: +{
        +  "description": "ID of the agent",
        +  "type": "integer"
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "description": "ID of the task",
        -  "type": "integer"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "task_id",
        -  "operation"
        -]New value: +[
        +  "agent_id",
        +  "operation"
        +]
  8. 15 tool updates
    • Addedclassify_segment_group
    • Addedcorrect_segment_classification
    • Addedcreate_segment_group
    • Changeddefine_sequence2 fields changed
      • changedInput schema / properties / sequence / properties / nodes / items / oneOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
        -    "properties": {
        -      "accepted": {
        -        "type": "string"
        -      },
        -      "already_connected": {
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "connection_request",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "timeout": {
        -        "anyOf": [
        -          {
        -            "additionalProperties": false,
        -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        -            "properties": {
        -              "delay": {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit` (w/d/h/m). Validated by construction — no\nfree-form string to parse — and stored JSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              "to": {
        -                "type": "string"
        -              }
        -            },
        -            "required": [
        -              "delay",
        -              "to"
        -            ],
        -            "title": "TimedAdvance",
        -            "type": "object"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "accepted",
        -      "already_connected"
        -    ],
        -    "title": "ConnectionRequestNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).",
        -    "properties": {
        -      "action": {
        -        "enum": [
        -          "message",
        -          "inmail",
        -          "email"
        -        ],
        -        "type": "string"
        -      },
        -      "channel": {
        -        "enum": [
        -          "linkedin",
        -          "email"
        -        ],
        -        "type": "string"
        -      },
        -      "follow_up": {
        -        "anyOf": [
        -          {
        -            "additionalProperties": false,
        -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        -            "properties": {
        -              "delay": {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit` (w/d/h/m). Validated by construction — no\nfree-form string to parse — and stored JSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              "to": {
        -                "type": "string"
        -              }
        -            },
        -            "required": [
        -              "delay",
        -              "to"
        -            ],
        -            "title": "TimedAdvance",
        -            "type": "object"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "send",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "replied": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "channel",
        -      "action",
        -      "replied"
        -    ],
        -    "title": "SendNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
        -    "properties": {
        -      "action": {
        -        "enum": [
        -          "follow",
        -          "withdraw",
        -          "resolve",
        -          "comment",
        -          "reaction"
        -        ],
        -        "type": "string"
        -      },
        -      "channel": {
        -        "const": "linkedin",
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "action",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "notify": {
        -        "default": false,
        -        "type": "boolean"
        -      },
        -      "then": {
        -        "additionalProperties": false,
        -        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
        -        "properties": {
        -          "after": {
        -            "anyOf": [
        -              {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit` (w/d/h/m). Validated by construction — no\nfree-form string to parse — and stored JSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              {
        -                "type": "null"
        -              }
        -            ],
        -            "default": null
        -          },
        -          "to": {
        -            "type": "string"
        -          }
        -        },
        -        "required": [
        -          "to"
        -        ],
        -        "title": "Advance",
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "channel",
        -      "action",
        -      "then"
        -    ],
        -    "title": "SilentActionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
        -    "properties": {
        -      "action_description": {
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "manual",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "then": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "action_description",
        -      "then"
        -    ],
        -    "title": "ManualActionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a send node's template and a terminal node's hook — not a field\nhere. Unlike a manual node it boots an agent run (rather than waiting on a human) and unlike\na terminal hook it advances; structurally it is a decision with one implicit 'always' arm —\nthe run does the side-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
        -    "properties": {
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "automation",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "then": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "then"
        -    ],
        -    "title": "AutomationNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
        -    "properties": {
        -      "arms": {
        -        "items": {
        -          "additionalProperties": false,
        -          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
        -          "properties": {
        -            "case": {
        -              "minLength": 1,
        -              "type": "string"
        -            },
        -            "to": {
        -              "type": "string"
        -            }
        -          },
        -          "required": [
        -            "case",
        -            "to"
        -          ],
        -          "title": "Arm",
        -          "type": "object"
        -        },
        -        "minItems": 1,
        -        "type": "array"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "decision",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "rule": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "rule",
        -      "arms"
        -    ],
        -    "title": "DecisionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
        -    "properties": {
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "terminal",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind"
        -    ],
        -    "title": "TerminalNode",
        -    "type": "object"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
        +    "properties": {
        +      "accepted": {
        +        "type": "string"
        +      },
        +      "already_connected": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "connection_request",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "timeout": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        +            "properties": {
        +              "delay": {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              "to": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "delay",
        +              "to"
        +            ],
        +            "title": "TimedAdvance",
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "accepted",
        +      "already_connected"
        +    ],
        +    "title": "ConnectionRequestNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).",
        +    "properties": {
        +      "action": {
        +        "enum": [
        +          "message",
        +          "inmail",
        +          "email"
        +        ],
        +        "type": "string"
        +      },
        +      "channel": {
        +        "enum": [
        +          "linkedin",
        +          "email"
        +        ],
        +        "type": "string"
        +      },
        +      "follow_up": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        +            "properties": {
        +              "delay": {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              "to": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "delay",
        +              "to"
        +            ],
        +            "title": "TimedAdvance",
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "send",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "replied": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "channel",
        +      "action",
        +      "replied"
        +    ],
        +    "title": "SendNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
        +    "properties": {
        +      "action": {
        +        "enum": [
        +          "follow",
        +          "withdraw",
        +          "resolve",
        +          "comment",
        +          "reaction"
        +        ],
        +        "type": "string"
        +      },
        +      "channel": {
        +        "const": "linkedin",
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "action",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "notify": {
        +        "default": false,
        +        "type": "boolean"
        +      },
        +      "then": {
        +        "additionalProperties": false,
        +        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
        +        "properties": {
        +          "after": {
        +            "anyOf": [
        +              {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "default": null
        +          },
        +          "to": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "to"
        +        ],
        +        "title": "Advance",
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "channel",
        +      "action",
        +      "then"
        +    ],
        +    "title": "SilentActionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
        +    "properties": {
        +      "action_description": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "manual",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "then": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "action_description",
        +      "then"
        +    ],
        +    "title": "ManualActionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a send node's template and a terminal node's hook — not a field\nhere. Unlike a manual node it boots an agent run (rather than waiting on a human) and unlike\na terminal hook it advances; structurally it is a decision with one implicit 'always' arm —\nthe run does the side-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
        +    "properties": {
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "automation",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "then": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "then"
        +    ],
        +    "title": "AutomationNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
        +    "properties": {
        +      "arms": {
        +        "items": {
        +          "additionalProperties": false,
        +          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
        +          "properties": {
        +            "case": {
        +              "minLength": 1,
        +              "type": "string"
        +            },
        +            "to": {
        +              "type": "string"
        +            }
        +          },
        +          "required": [
        +            "case",
        +            "to"
        +          ],
        +          "title": "Arm",
        +          "type": "object"
        +        },
        +        "minItems": 1,
        +        "type": "array"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "decision",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "rule": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "rule",
        +      "arms"
        +    ],
        +    "title": "DecisionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
        +    "properties": {
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "terminal",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind"
        +    ],
        +    "title": "TerminalNode",
        +    "type": "object"
        +  }
        +]
      • changedInput schema / properties / sequence / properties / start / properties / entry / properties / after / anyOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "description": "A timed hold: `value` units of `unit` (w/d/h/m). Validated by construction — no\nfree-form string to parse — and stored JSON-natively as `{value, unit}`.",
        -    "properties": {
        -      "unit": {
        -        "enum": [
        -          "w",
        -          "d",
        -          "h",
        -          "m"
        -        ],
        -        "type": "string"
        -      },
        -      "value": {
        -        "exclusiveMinimum": 0,
        -        "type": "integer"
        -      }
        -    },
        -    "required": [
        -      "value",
        -      "unit"
        -    ],
        -    "title": "EdgeDelay",
        -    "type": "object"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +    "properties": {
        +      "unit": {
        +        "enum": [
        +          "w",
        +          "cd",
        +          "d",
        +          "h",
        +          "m"
        +        ],
        +        "type": "string"
        +      },
        +      "value": {
        +        "exclusiveMinimum": 0,
        +        "type": "integer"
        +      }
        +    },
        +    "required": [
        +      "value",
        +      "unit"
        +    ],
        +    "title": "EdgeDelay",
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
    • Addeddelete_segment_group
    • Addeddelete_segment_tag
    • Addedget_segment_rates
    • Changedget_skill_guide1 field changed
      • changedInput schema / properties / skill / enum
        Previous value: -[
        -  "blog_content_calendar",
        -  "blog_post_writing",
        -  "company_monitor",
        -  "creating_todos",
        -  "email_inbox_management",
        -  "email_sequence_setup",
        -  "exa_find_people",
        -  "find_companies_news_scan",
        -  "find_companies_news_scan_setup",
        -  "find_companies_search_criteria",
        -  "find_companies_webset_mode",
        -  "find_people",
        -  "find_warm_intro_paths",
        -  "linkedin_flowless_campaigns",
        -  "linkedin_outreach",
        -  "linkedin_outreach_debugging",
        -  "linkedin_outreach_finding_leads",
        -  "linkedin_outreach_operations",
        -  "linkedin_outreach_warmup",
        -  "linkedin_post_monitoring",
        -  "linkedin_post_writing",
        -  "message_tagging",
        -  "mixed_channel_outreach",
        -  "news_scan_predictleads",
        -  "news_scan_tavily",
        -  "news_scan_theirstack",
        -  "posthog_setup",
        -  "prospecting_agent",
        -  "scheduling_calendar_events",
        -  "social_listening",
        -  "trigger_code",
        -  "weekly_sales_review"
        -]New value: +[
        +  "blog_content_calendar",
        +  "blog_post_writing",
        +  "company_monitor",
        +  "creating_todos",
        +  "email_inbox_management",
        +  "email_sequence_setup",
        +  "exa_find_people",
        +  "find_companies_news_scan",
        +  "find_companies_news_scan_setup",
        +  "find_companies_search_criteria",
        +  "find_companies_webset_mode",
        +  "find_people",
        +  "find_warm_intro_paths",
        +  "linkedin_flowless_campaigns",
        +  "linkedin_outreach",
        +  "linkedin_outreach_debugging",
        +  "linkedin_outreach_finding_leads",
        +  "linkedin_outreach_operations",
        +  "linkedin_outreach_warmup",
        +  "linkedin_post_monitoring",
        +  "linkedin_post_writing",
        +  "message_tagging",
        +  "mixed_channel_outreach",
        +  "news_scan_predictleads",
        +  "news_scan_tavily",
        +  "news_scan_theirstack",
        +  "posthog_setup",
        +  "prospecting_agent",
        +  "scheduling_calendar_events",
        +  "segment_tagging",
        +  "social_listening",
        +  "trigger_code",
        +  "weekly_sales_review"
        +]
    • Addedlist_segment_groups
    • Changedquery_people3 fields changed
      • changedInput schema / properties / group_by / anyOf
        Previous value: -[
        -  {
        -    "enum": [
        -      "title",
        -      "company",
        -      "location",
        -      "outreach_stage"
        -    ],
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "enum": [
        +      "title",
        +      "company",
        +      "location",
        +      "outreach_stage",
        +      "segment"
        +    ],
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / group_by / description
        Previous value: -"Aggregate mode, returned instead of the row list — whole-set per-bucket counts\nover all your people (not capped by `limit`). \"title\" / \"company\" / \"location\" bucket\nby that firmographic; \"outreach_stage\" by the person's collapsed lead-funnel bucket.\nOmit for the row list."New value: +"Aggregate mode, returned instead of the row list — whole-set per-bucket counts\nover all your people (not capped by `limit`). \"title\" / \"company\" / \"location\" bucket\nby that firmographic; \"outreach_stage\" by the person's collapsed lead-funnel bucket;\n\"segment\" by the person's tag in the `segment_group_id` group. Omit for the row list."
      • addedInput schema / properties / segment_group_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Required with group_by=\"segment\" — the segment group (from\nlist_segment_groups) to bucket by. Ignored otherwise."
        +}
    • Addedquery_segment_people
    • Changedtheirstack_search3 fields changed
      • addedInput schema / properties / list_name
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Short kebab slug naming the Output-tab list bucket (e.g.\n'hiring-sdrs'). Absent, companies land in the 'default' list."
        +}
      • addedInput schema / properties / persist
        Added value: +{
        +  "default": true,
        +  "description": "Default True — in a task, the companies are saved as company\nrows automatically (see above). Pass False to return the postings\nwithout saving, for a read-only query or a flow that records a\nvalidated/filtered subset itself.",
        +  "type": "boolean"
        +}
      • addedInput schema / properties / task_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Optional — a specific task to save the companies into. Omit\nit to use the running task, which is the usual case. Only a\nthrowaway lookup outside any task returns postings without saving."
        +}
    • Changedupdate_node1 field changed
      • changedInput schema / properties / node / oneOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
        -    "properties": {
        -      "accepted": {
        -        "type": "string"
        -      },
        -      "already_connected": {
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "connection_request",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "timeout": {
        -        "anyOf": [
        -          {
        -            "additionalProperties": false,
        -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        -            "properties": {
        -              "delay": {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit` (w/d/h/m). Validated by construction — no\nfree-form string to parse — and stored JSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              "to": {
        -                "type": "string"
        -              }
        -            },
        -            "required": [
        -              "delay",
        -              "to"
        -            ],
        -            "title": "TimedAdvance",
        -            "type": "object"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "accepted",
        -      "already_connected"
        -    ],
        -    "title": "ConnectionRequestNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).",
        -    "properties": {
        -      "action": {
        -        "enum": [
        -          "message",
        -          "inmail",
        -          "email"
        -        ],
        -        "type": "string"
        -      },
        -      "channel": {
        -        "enum": [
        -          "linkedin",
        -          "email"
        -        ],
        -        "type": "string"
        -      },
        -      "follow_up": {
        -        "anyOf": [
        -          {
        -            "additionalProperties": false,
        -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        -            "properties": {
        -              "delay": {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit` (w/d/h/m). Validated by construction — no\nfree-form string to parse — and stored JSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              "to": {
        -                "type": "string"
        -              }
        -            },
        -            "required": [
        -              "delay",
        -              "to"
        -            ],
        -            "title": "TimedAdvance",
        -            "type": "object"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "default": null
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "send",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "replied": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "channel",
        -      "action",
        -      "replied"
        -    ],
        -    "title": "SendNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
        -    "properties": {
        -      "action": {
        -        "enum": [
        -          "follow",
        -          "withdraw",
        -          "resolve",
        -          "comment",
        -          "reaction"
        -        ],
        -        "type": "string"
        -      },
        -      "channel": {
        -        "const": "linkedin",
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "action",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "notify": {
        -        "default": false,
        -        "type": "boolean"
        -      },
        -      "then": {
        -        "additionalProperties": false,
        -        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
        -        "properties": {
        -          "after": {
        -            "anyOf": [
        -              {
        -                "additionalProperties": false,
        -                "description": "A timed hold: `value` units of `unit` (w/d/h/m). Validated by construction — no\nfree-form string to parse — and stored JSON-natively as `{value, unit}`.",
        -                "properties": {
        -                  "unit": {
        -                    "enum": [
        -                      "w",
        -                      "d",
        -                      "h",
        -                      "m"
        -                    ],
        -                    "type": "string"
        -                  },
        -                  "value": {
        -                    "exclusiveMinimum": 0,
        -                    "type": "integer"
        -                  }
        -                },
        -                "required": [
        -                  "value",
        -                  "unit"
        -                ],
        -                "title": "EdgeDelay",
        -                "type": "object"
        -              },
        -              {
        -                "type": "null"
        -              }
        -            ],
        -            "default": null
        -          },
        -          "to": {
        -            "type": "string"
        -          }
        -        },
        -        "required": [
        -          "to"
        -        ],
        -        "title": "Advance",
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "channel",
        -      "action",
        -      "then"
        -    ],
        -    "title": "SilentActionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
        -    "properties": {
        -      "action_description": {
        -        "type": "string"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "manual",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "then": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "action_description",
        -      "then"
        -    ],
        -    "title": "ManualActionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a send node's template and a terminal node's hook — not a field\nhere. Unlike a manual node it boots an agent run (rather than waiting on a human) and unlike\na terminal hook it advances; structurally it is a decision with one implicit 'always' arm —\nthe run does the side-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
        -    "properties": {
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "automation",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "then": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "then"
        -    ],
        -    "title": "AutomationNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
        -    "properties": {
        -      "arms": {
        -        "items": {
        -          "additionalProperties": false,
        -          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
        -          "properties": {
        -            "case": {
        -              "minLength": 1,
        -              "type": "string"
        -            },
        -            "to": {
        -              "type": "string"
        -            }
        -          },
        -          "required": [
        -            "case",
        -            "to"
        -          ],
        -          "title": "Arm",
        -          "type": "object"
        -        },
        -        "minItems": 1,
        -        "type": "array"
        -      },
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "decision",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      },
        -      "rule": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind",
        -      "rule",
        -      "arms"
        -    ],
        -    "title": "DecisionNode",
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
        -    "properties": {
        -      "id": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "terminal",
        -        "type": "string"
        -      },
        -      "label": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "id",
        -      "label",
        -      "kind"
        -    ],
        -    "title": "TerminalNode",
        -    "type": "object"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
        +    "properties": {
        +      "accepted": {
        +        "type": "string"
        +      },
        +      "already_connected": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "connection_request",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "timeout": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        +            "properties": {
        +              "delay": {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              "to": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "delay",
        +              "to"
        +            ],
        +            "title": "TimedAdvance",
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "accepted",
        +      "already_connected"
        +    ],
        +    "title": "ConnectionRequestNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).",
        +    "properties": {
        +      "action": {
        +        "enum": [
        +          "message",
        +          "inmail",
        +          "email"
        +        ],
        +        "type": "string"
        +      },
        +      "channel": {
        +        "enum": [
        +          "linkedin",
        +          "email"
        +        ],
        +        "type": "string"
        +      },
        +      "follow_up": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
        +            "properties": {
        +              "delay": {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              "to": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "delay",
        +              "to"
        +            ],
        +            "title": "TimedAdvance",
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "default": null
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "send",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "replied": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "channel",
        +      "action",
        +      "replied"
        +    ],
        +    "title": "SendNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
        +    "properties": {
        +      "action": {
        +        "enum": [
        +          "follow",
        +          "withdraw",
        +          "resolve",
        +          "comment",
        +          "reaction"
        +        ],
        +        "type": "string"
        +      },
        +      "channel": {
        +        "const": "linkedin",
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "action",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "notify": {
        +        "default": false,
        +        "type": "boolean"
        +      },
        +      "then": {
        +        "additionalProperties": false,
        +        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
        +        "properties": {
        +          "after": {
        +            "anyOf": [
        +              {
        +                "additionalProperties": false,
        +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
        +                "properties": {
        +                  "unit": {
        +                    "enum": [
        +                      "w",
        +                      "cd",
        +                      "d",
        +                      "h",
        +                      "m"
        +                    ],
        +                    "type": "string"
        +                  },
        +                  "value": {
        +                    "exclusiveMinimum": 0,
        +                    "type": "integer"
        +                  }
        +                },
        +                "required": [
        +                  "value",
        +                  "unit"
        +                ],
        +                "title": "EdgeDelay",
        +                "type": "object"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "default": null
        +          },
        +          "to": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "to"
        +        ],
        +        "title": "Advance",
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "channel",
        +      "action",
        +      "then"
        +    ],
        +    "title": "SilentActionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
        +    "properties": {
        +      "action_description": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "manual",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "then": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "action_description",
        +      "then"
        +    ],
        +    "title": "ManualActionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a send node's template and a terminal node's hook — not a field\nhere. Unlike a manual node it boots an agent run (rather than waiting on a human) and unlike\na terminal hook it advances; structurally it is a decision with one implicit 'always' arm —\nthe run does the side-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
        +    "properties": {
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "automation",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "then": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "then"
        +    ],
        +    "title": "AutomationNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
        +    "properties": {
        +      "arms": {
        +        "items": {
        +          "additionalProperties": false,
        +          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
        +          "properties": {
        +            "case": {
        +              "minLength": 1,
        +              "type": "string"
        +            },
        +            "to": {
        +              "type": "string"
        +            }
        +          },
        +          "required": [
        +            "case",
        +            "to"
        +          ],
        +          "title": "Arm",
        +          "type": "object"
        +        },
        +        "minItems": 1,
        +        "type": "array"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "decision",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "rule": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind",
        +      "rule",
        +      "arms"
        +    ],
        +    "title": "DecisionNode",
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
        +    "properties": {
        +      "id": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "terminal",
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "kind"
        +    ],
        +    "title": "TerminalNode",
        +    "type": "object"
        +  }
        +]
    • Addedupdate_segment_group
    • Addedupdate_segment_tag
  9. 4 tool updates
    • Changedexa_find_people3 fields changed
      • changedInput schema / properties / list_name / description
        Previous value: -"Short kebab slug naming the list bucket (e.g.\n'acme-execs'). Pass together with `task_id`. When set, the\nmatched people are also saved as `agent_search_results` person\nrows under this slug (deduped by profile, re-runs update in\nplace) — query them by reference with `query_search_results`\ninstead of re-typing URLs from `results`. The `results` return\nis unchanged either way."New value: +"Short kebab slug naming the Output-tab list bucket (e.g.\n'acme-execs'). When this runs in a task, matched people are saved\nand linked as `agent_search_results` person rows automatically —\ndeduped by profile, re-runs update in place; `list_name` names\ntheir list, and absent it they land in the 'default' list. Query\nthem by reference with `query_search_results` instead of re-typing\nURLs from `results`. The `results` return is unchanged either way."
      • addedInput schema / properties / persist
        Added value: +{
        +  "default": true,
        +  "description": "Default True — in a task, matched people are saved and linked\nas person rows automatically (see `list_name`). Pass False to return\nresults without saving, for a flow that folds these people into\nanother row instead — e.g. a company-find that nests them under each\ncompany row via `record_search_results`, where a standalone person\nlist would duplicate people already shown under their company.",
        +  "type": "boolean"
        +}
      • changedInput schema / properties / task_id / description
        Previous value: -"Pass together with `list_name` to save every matched\nperson into this task's Output-tab list. Omit both for a\nthrowaway lookup."New value: +"Optional — a specific task to save the matched people into.\nOmit it to use the running task, which is the usual case. Only a\nthrowaway lookup outside any task returns results without saving."
    • Changedfetch_post_engagers3 fields changed
      • changedInput schema / properties / list_name / description
        Previous value: -"Short kebab slug naming the list bucket (e.g.\n'launch-post-engagers'). Pass together with `task_id`."New value: +"Short kebab slug naming the list bucket (e.g.\n'launch-post-engagers'). Absent, engagers land in the 'default' list."
      • addedInput schema / properties / persist
        Added value: +{
        +  "default": true,
        +  "description": "Default True — in a task, every engager is saved as a person\nrow automatically (see `list_name`). Pass False to fetch the\nengagers without saving them as person rows: the raw engagement rows\nstill land and stay queryable via `query_linkedin_post_engagements`,\nbut no `agent_search_results` list is written — for a flow that\nrecords only a filtered subset itself, so the full unfiltered list\nwouldn't also clutter the Output tab.",
        +  "type": "boolean"
        +}
      • changedInput schema / properties / task_id / description
        Previous value: -"Pass together with `list_name` to materialize the full\nunfiltered engager list into this task's search results. When\nthe user wants only a subset (ICP fit, founders only, a\nspecific role), omit both — qualify via\nquery_linkedin_post_engagements first, then persist the keepers\nwith record_search_results."New value: +"Optional — a specific task to materialize the engager list\ninto. Omit it to use the running task, which is the usual case."
    • Changedget_luma_guests1 field changed
      • addedInput schema / properties / list_name
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Short kebab slug naming the Output-tab list bucket. Absent,\nguests land in the 'default' list."
        +}
    • Changedsearch_linkedin_people1 field changed
      • changedInput schema / properties / list_name / description
        Previous value: -"Optional short slug naming the Output-tab list to save under.\n      When set (and a task is active), this search's matches are saved\n      there, deduped by profile. Reuse the same slug across a loop or\n      follow-up searches to gather them into one list."New value: +"Optional short slug naming the Output-tab list the matches are\n      saved under when this runs in a task. Absent, they save to the\n      'default' list. Reuse the same slug across a loop or follow-up\n      searches to gather them into one list (deduped by profile)."
  10. 6 tool updates
    • Changedclassify_message_tag_group2 fields changed
      • addedInput schema / properties / senders
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "In a shared workspace, the teammate email addresses whose sent messages to\ntag (and charge for). Omit to tag every sender in the workspace. Any email not in\nthe workspace is dropped; if that leaves no valid teammate, nothing is tagged — it\ndoes NOT fall back to the whole workspace."
        +}
      • addedInput schema / properties / task_ids
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "integer"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Scope classification to specific campaigns (agent_tasks ids from a group's\n`classifiable_campaigns`); omit for every campaign. `[]` classifies zero."
        +}
    • Changedget_message_tag_cross_tab3 fields changed
      • addedInput schema / properties / senders
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "In a shared workspace, the teammate email addresses whose messages to score.\nOmit to score every sender in the workspace. Any email not in the workspace is\ndropped; if that leaves no valid teammate, the result is empty — it does NOT fall\nback to the whole workspace."
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Scope to one campaign (an agent_tasks id from a group's `campaigns`); omit for\nall campaigns. Campaign membership is the prospect's current one."
        -}
      • addedInput schema / properties / task_ids
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "integer"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Scope to one or more campaigns (agent_tasks ids from a group's `campaigns`);\nomit for all campaigns. Campaign membership is the prospect's current one."
        +}
    • Changedget_message_tag_rates4 fields changed
      • addedInput schema / properties / position
        Added value: +{
        +  "anyOf": [
        +    {
        +      "enum": [
        +        "first",
        +        "follow_up"
        +      ],
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "'first' (each prospect's opener on a channel) or 'follow_up' (its later sends\non a channel); omit for both."
        +}
      • addedInput schema / properties / senders
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "In a shared workspace, the teammate email addresses whose messages to score.\nOmit to score every sender in the workspace. Any email not in the workspace is\ndropped; if that leaves no valid teammate, the result is empty — it does NOT fall\nback to the whole workspace."
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Scope to one campaign (an agent_tasks id from a group's `campaigns`); omit for\nall campaigns. Campaign membership is the prospect's current one."
        -}
      • addedInput schema / properties / task_ids
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "integer"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Scope to one or more campaigns (agent_tasks ids from a group's `campaigns`);\nomit for all campaigns. Campaign membership is the prospect's current one."
        +}
    • Changedget_skill_guide1 field changed
      • changedInput schema / properties / skill / enum
        Previous value: -[
        -  "blog_content_calendar",
        -  "blog_post_writing",
        -  "company_monitor",
        -  "creating_todos",
        -  "email_inbox_management",
        -  "email_sequence_setup",
        -  "exa_find_people",
        -  "find_companies_news_scan",
        -  "find_companies_news_scan_setup",
        -  "find_companies_search_criteria",
        -  "find_companies_webset_mode",
        -  "find_people",
        -  "find_warm_intro_paths",
        -  "linkedin_flowless_campaigns",
        -  "linkedin_outreach",
        -  "linkedin_outreach_debugging",
        -  "linkedin_outreach_finding_leads",
        -  "linkedin_outreach_operations",
        -  "linkedin_outreach_warmup",
        -  "linkedin_post_monitoring",
        -  "linkedin_post_writing",
        -  "mixed_channel_outreach",
        -  "news_scan_predictleads",
        -  "news_scan_tavily",
        -  "news_scan_theirstack",
        -  "posthog_setup",
        -  "prospecting_agent",
        -  "scheduling_calendar_events",
        -  "social_listening",
        -  "trigger_code",
        -  "weekly_sales_review"
        -]New value: +[
        +  "blog_content_calendar",
        +  "blog_post_writing",
        +  "company_monitor",
        +  "creating_todos",
        +  "email_inbox_management",
        +  "email_sequence_setup",
        +  "exa_find_people",
        +  "find_companies_news_scan",
        +  "find_companies_news_scan_setup",
        +  "find_companies_search_criteria",
        +  "find_companies_webset_mode",
        +  "find_people",
        +  "find_warm_intro_paths",
        +  "linkedin_flowless_campaigns",
        +  "linkedin_outreach",
        +  "linkedin_outreach_debugging",
        +  "linkedin_outreach_finding_leads",
        +  "linkedin_outreach_operations",
        +  "linkedin_outreach_warmup",
        +  "linkedin_post_monitoring",
        +  "linkedin_post_writing",
        +  "message_tagging",
        +  "mixed_channel_outreach",
        +  "news_scan_predictleads",
        +  "news_scan_tavily",
        +  "news_scan_theirstack",
        +  "posthog_setup",
        +  "prospecting_agent",
        +  "scheduling_calendar_events",
        +  "social_listening",
        +  "trigger_code",
        +  "weekly_sales_review"
        +]
    • Changedquery_tagged_messages4 fields changed
      • addedInput schema / properties / position
        Added value: +{
        +  "anyOf": [
        +    {
        +      "enum": [
        +        "first",
        +        "follow_up"
        +      ],
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "'first' (each prospect's opener on a channel) or 'follow_up' (its later sends\non a channel); omit for both."
        +}
      • addedInput schema / properties / senders
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "In a shared workspace, the teammate email addresses whose messages to return.\nOmit to return every sender's. Any email not in the workspace is dropped; if that\nleaves no valid teammate, the result is empty — it does NOT fall back to the whole\nworkspace."
        +}
      • removedInput schema / properties / task_id
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ],
        -  "default": null,
        -  "description": "Scope to one campaign (an agent_tasks id from a group's `campaigns`); omit for\nall campaigns. Campaign membership is the prospect's current one."
        -}
      • addedInput schema / properties / task_ids
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "type": "integer"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Scope to one or more campaigns (agent_tasks ids from a group's `campaigns`);\nomit for all campaigns. Campaign membership is the prospect's current one."
        +}
    • Changedupdate_prospect1 field changed
      • changedInput schema / properties / email / description
        Previous value: -"An address to store on the prospect's email column (optional). Fills in a\nprospect that reached an email step without one, so a retried step can send.\nOverwrites this prospect's existing address; refused if the address already\nbelongs to another prospect in the account (reconcile the duplicate instead).\nLeave unset for ordinary stage/priority updates."New value: +"An address to store for this prospect (optional). Fills in a prospect that\nreached an email step without one, so a retried step can send. Lands on the\ncampaign row and on the person's profile, overwriting either's existing\naddress; refused if the address already belongs to another prospect in the\naccount (reconcile the duplicate instead). Leave unset for ordinary\nstage/priority updates."
  11. 17 tool updates
    • Addedcorrect_message_classification
    • Addedcreate_agent
    • Removedcreate_task
    • Addeddelete_agent
    • Removeddelete_task
    • Addedget_agent_details
    • Addedget_message_tag_cross_tab
    • Changedget_message_tag_rates1 field changed
      • addedInput schema / properties / task_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Scope to one campaign (an agent_tasks id from a group's `campaigns`); omit for\nall campaigns. Campaign membership is the prospect's current one."
        +}
    • Changedget_run_transcript1 field changed
      • changedInput schema / properties / run_id / description
        Previous value: -"An agent-run id — the `id` of a `get_task_details` `recent_runs`\nentry (also shown as `(run N)` in the task's Recent Activity feed)."New value: +"An agent-run id — the `id` of a `get_agent_details` `recent_runs`\nentry (also shown as `(run N)` in the task's Recent Activity feed)."
    • Removedget_task_details
    • Addedlist_agents
    • Removedlist_tasks
    • Changedmanage_email_outreach_queue2 fields changed
      • changedInput schema / properties / action / description
        Previous value: -"One of:\n- 'status': Get full queue details (pending/sent/failed items). The\n  `pending_items` / `pending_approval_items` lists cap at 50 each, each\n  with a `pending_truncated` / `pending_approval_truncated` flag — when a\n  list's flag is True, pass `offset` (offset += 50) to read the next page.\n  Each item carries the `mailbox` it sends from; the account-level\n  `mailboxes` list gives each mailbox's remaining daily headroom. The\n  top-level `cancelled_by_kind` maps each cancel cause to its count\n  (`recipient_replied`, `task_deleted`, `auto_shelved`, …; pre-taxonomy\n  rows under `unknown`) — read it to answer \"how much outreach was\n  cancelled, and why?\".\n- 'update': Edit a queued email's subject and/or body. Requires recipient_email.\n  Pass subject and/or body to change — no cancel-and-re-queue needed. Send order\n  is derived, not per-row scheduled; to pause or resume the campaign use\n  update_task (see the note above).\n- 'approve': Send authorization belongs to the user, so this action\n  needs a message that arrived after the drafts were queued and\n  explicitly says to approve or send them. A message from before the\n  drafts existed cannot authorize them — a request to review them,\n  to approve them later, or to be able to approve from chat means:\n  report that the queue is awaiting approval and end your turn; the\n  user's next message decides. Once authorized: if recipient_email\n  is given, approve only that item, otherwise approve ALL\n  pending_approval items. From account-level chat an approve-all is\n  rejected unless task_id names the campaign — it must not fire\n  every campaign's drafts into sending at once.\n- 'cancel': Cancel a queued email. Pass recipient_email to cancel that one\n  person. To cancel the ENTIRE pending queue — which drops drafts the user\n  already approved — you must pass\n  cancel_all=True; an unscoped cancel without it is rejected. From\n  account-level chat a cancel_all is rejected unless task_id names the campaign\n  to cancel — it must not clear every campaign's queue at once."New value: +"One of:\n- 'status': Get full queue details (pending/sent/failed items). The\n  `pending_items` / `pending_approval_items` lists cap at 50 each, each\n  with a `pending_truncated` / `pending_approval_truncated` flag — when a\n  list's flag is True, pass `offset` (offset += 50) to read the next page.\n  Each item carries the `mailbox` it sends from; the account-level\n  `mailboxes` list gives each mailbox's remaining daily headroom. The\n  top-level `cancelled_by_kind` maps each cancel cause to its count\n  (`recipient_replied`, `task_deleted`, `auto_shelved`, …; pre-taxonomy\n  rows under `unknown`) — read it to answer \"how much outreach was\n  cancelled, and why?\".\n- 'update': Edit a queued email's subject and/or body. Requires recipient_email.\n  Pass subject and/or body to change — no cancel-and-re-queue needed. Send order\n  is derived, not per-row scheduled; to pause or resume the campaign use\n  update_agent (see the note above).\n- 'approve': Send authorization belongs to the user, so this action\n  needs a message that arrived after the drafts were queued and\n  explicitly says to approve or send them. A message from before the\n  drafts existed cannot authorize them — a request to review them,\n  to approve them later, or to be able to approve from chat means:\n  report that the queue is awaiting approval and end your turn; the\n  user's next message decides. Once authorized: if recipient_email\n  is given, approve only that item, otherwise approve ALL\n  pending_approval items. From account-level chat an approve-all is\n  rejected unless task_id names the campaign — it must not fire\n  every campaign's drafts into sending at once.\n- 'cancel': Cancel a queued email. Pass recipient_email to cancel that one\n  person. To cancel the ENTIRE pending queue — which drops drafts the user\n  already approved — you must pass\n  cancel_all=True; an unscoped cancel without it is rejected. From\n  account-level chat a cancel_all is rejected unless task_id names the campaign\n  to cancel — it must not clear every campaign's queue at once."
      • changedInput schema / properties / task_id / description
        Previous value: -"For 'cancel' and 'approve' — scope a bulk cancel_all / approve-all to one\n    campaign's queue. Required from account-level chat (no active task), where an\n    unscoped bulk cancel or approve is rejected; call list_tasks to get the id."New value: +"For 'cancel' and 'approve' — scope a bulk cancel_all / approve-all to one\n    campaign's queue. Required from account-level chat (no active task), where an\n    unscoped bulk cancel or approve is rejected; call list_agents to get the id."
    • Changedmanage_linkedin_invite_queue1 field changed
      • changedInput schema / properties / task_id / description
        Previous value: -"For 'cancel' and 'approve' — scope a bulk cancel_all / approve-all to one\n    campaign's queue. Required from account-level chat (no active task), where an\n    unscoped bulk cancel or approve is rejected; call list_tasks to get the id."New value: +"For 'cancel' and 'approve' — scope a bulk cancel_all / approve-all to one\n    campaign's queue. Required from account-level chat (no active task), where an\n    unscoped bulk cancel or approve is rejected; call list_agents to get the id."
    • Changedquery_tagged_messages1 field changed
      • addedInput schema / properties / task_id
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Scope to one campaign (an agent_tasks id from a group's `campaigns`); omit for\nall campaigns. Campaign membership is the prospect's current one."
        +}
    • Addedupdate_agent
    • Removedupdate_task
  12. 5 tool updates
    • Changedquery_companies1 field changed
      • addedInput schema / properties / group_by
        Added value: +{
        +  "anyOf": [
        +    {
        +      "enum": [
        +        "industry",
        +        "location"
        +      ],
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Aggregate mode, returned instead of the row list — whole-set per-bucket counts\nover all your companies (not capped by `limit`), bucketed by \"industry\" or \"location\".\nOmit for the row list."
        +}
    • Changedquery_people1 field changed
      • addedInput schema / properties / group_by
        Added value: +{
        +  "anyOf": [
        +    {
        +      "enum": [
        +        "title",
        +        "company",
        +        "location",
        +        "outreach_stage"
        +      ],
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Aggregate mode, returned instead of the row list — whole-set per-bucket counts\nover all your people (not capped by `limit`). \"title\" / \"company\" / \"location\" bucket\nby that firmographic; \"outreach_stage\" by the person's collapsed lead-funnel bucket.\nOmit for the row list."
        +}
    • Changedrecord_search_results1 field changed
      • addedInput schema / properties / results / items / properties / data / properties / network_distance
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null
        +}
    • Changedretry_blocked_enactment1 field changed
      • changedInput schema / properties / linkedin_url / description
        Previous value: -"A corrected LinkedIn profile URL or slug (optional). Applied as\nan unverified claim the retried send verifies through the profile\nresolver, so a wrong-person pairing is held for adjudication like any\nother. Refused once a live lookup verified the current profile — a\ndifferent URL then means a different person: remove them from the\ncampaign and track the correct profile instead."New value: +"A corrected LinkedIn profile URL or slug (optional). Applied as\nan unverified claim the retried send verifies through the profile\nresolver, so a wrong-person pairing is held for adjudication like any\nother. On a previously-verified profile (a dead or reassigned URL) the\nold verification is cleared and the corrected identity re-verifies\nfresh; refused only when a teammate's prospect verified the same\nperson — remove this prospect and track the correct profile instead."
    • Changedupdate_prospect2 fields changed
      • addedInput schema / properties / linkedin_url
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "A corrected or missing LinkedIn profile URL/slug to bind to THIS\nprospect in place (optional) — an unverified claim the next send verifies\nthrough the profile resolver; any prior verification of the old URL is\ncleared with it. Refused when the URL already belongs to another of the\nuser's prospects, or when a teammate's prospect verified the same person."
        +}
      • changedInput schema / properties / stage / description
        Previous value: -"New stage value (optional, e.g. \"skipped\")"New value: +"New stage value (optional) — e.g. 'skipped' to drop the person, 'pending'\nto reverse a skip and re-queue them."
  13. 134 tool updates
    • First observedadd_trigger
    • First observedapollo_enrich
    • First observedapollo_save
    • First observedapollo_search
    • First observedappend_learning
    • First observedattach_to_outreach
    • First observedcheck_email_daily_usage
    • First observedcheck_linkedin_daily_usage
    • First observedclassify_message_tag_group
    • First observedcorrect_reply_outcome
    • First observedcreate_calendar_event
    • First observedcreate_message_tag_group
    • First observedcreate_task
    • First observedcreate_todo
    • First observeddefine_sequence
    • First observeddelete_message_tag
    • First observeddelete_message_tag_group
    • First observeddelete_task
    • First observeddetach_from_outreach
    • First observeddraft_email
    • First observeddraft_reply
    • First observededit_monitor_members
    • First observedenrich_linkedin_profiles
    • First observedescalate_to_team
    • First observedexa_find_people
    • First observedexa_search_news
    • First observedextend_find_search
    • First observedfetch_linkedin_messages_with_person
    • First observedfetch_post_engagers
    • First observedfind_companies_by_tech_stack
    • First observedfind_email
    • First observedfind_linkedin_url
    • First observedfind_phone_number
    • First observedfind_warm_intro_paths
    • First observedgenerate_monitored_post_comment
    • First observedget_campaign_flow
    • First observedget_chat_messages
    • First observedget_credit_usage
    • First observedget_email_attachment
    • First observedget_emails
    • First observedget_integration_status
    • First observedget_linkedin_monitors
    • First observedget_linkedin_posts
    • First observedget_luma_events
    • First observedget_luma_guests
    • First observedget_mailbox_deliverability
    • First observedget_meeting_transcript
    • First observedget_message_tag_rates
    • First observedget_node_history
    • First observedget_outreach_approval
    • First observedget_outreach_window
    • First observedget_pending_approvals
    • First observedget_personalization_guide
    • First observedget_run_transcript
    • First observedget_skill_guide
    • First observedget_social_listening_config
    • First observedget_task_details
    • First observedget_tool_connect_url
    • First observedget_user_memory
    • First observedget_writing_style_guide
    • First observedgoogle_news_search
    • First observedlist_attachments
    • First observedlist_message_tag_groups
    • First observedlist_prospect_events
    • First observedlist_tasks
    • First observedlist_team_shared_agents
    • First observedlist_teammates
    • First observedmanage_email_outreach_queue
    • First observedmanage_inbox
    • First observedmanage_linkedin_invite_queue
    • First observedmark_monitor_events_notified
    • First observedmark_social_posts_notified
    • First observedmatches_icp
    • First observedmove_prospect_to_node
    • First observedquery_analytics
    • First observedquery_companies
    • First observedquery_linkedin_post_engagements
    • First observedquery_linkedin_posts
    • First observedquery_monitored_companies
    • First observedquery_monitored_posts
    • First observedquery_people
    • First observedquery_prospect_research
    • First observedquery_prospects
    • First observedquery_search_results
    • First observedquery_tagged_messages
    • First observedqueue_linkedin_post_engagement
    • First observedread_attachment
    • First observedread_email_attachment
    • First observedrecord_search_results
    • First observedregister_manual_linkedin_invites
    • First observedremove_trigger
    • First observedreport_enactment_blocked
    • First observedrequest_user_action
    • First observedresolve_date
    • First observedretry_blocked_enactment
    • First observedsave_memory
    • First observedsave_prospect_research
    • First observedsearch_calendar
    • First observedsearch_chat_history
    • First observedsearch_emails
    • First observedsearch_linkedin_connections
    • First observedsearch_linkedin_message_history
    • First observedsearch_linkedin_people
    • First observedsearch_meetings
    • First observedsearch_monitor_events
    • First observedsearch_news_events
    • First observedsearch_social_posts
    • First observedsearch_todos
    • First observedsearch_trends
    • First observedsend_email
    • First observedsend_luma_invites
    • First observedset_linkedin_daily_limits
    • First observedset_linkedin_warmup
    • First observedset_outreach_approval
    • First observedsetup_email_sequence
    • First observedsetup_linkedin_monitoring
    • First observedsetup_linkedin_sequence
    • First observedsetup_social_listening
    • First observedstart_find_search
    • First observedstop_linkedin_monitoring
    • First observedtheirstack_search
    • First observedtrack_monitored_companies
    • First observedtrack_prospects
    • First observedupdate_calendar_event
    • First observedupdate_message_tag
    • First observedupdate_message_tag_group
    • First observedupdate_monitored_company
    • First observedupdate_node
    • First observedupdate_onboarding_progress
    • First observedupdate_prospect
    • First observedupdate_task
    • First observedupdate_todo
    • First observedupdate_trigger
    • First observedupdate_workspace

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    B
    maintenance
    AI agent for LinkedIn outreach: finds the right people, writes in your voice, follows up, replies. Six campaign goals: sell a product or service, find a job, hire people, find partners or investors, find a vendor, research interviews. Campaigns stay drafts until you launch them.
    22
    9
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Connects AI assistants to LinkedIn outreach, enabling lead finding, campaign management, messaging, and analytics through natural language.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables self-driving LinkedIn prospecting from AI assistants, including finding leads, warming them up, inviting them, and opening conversations, with human approval required before any message is sent.
    1
    MIT
Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources