Well
Server Details
Connect your AI to your Well financial data - invoices, companies, contacts.
- Status
- Healthy
- Uptime
- 99.8% over 38 days
- Last Tested
- Transport
- Streamable HTTP · MCP 2025-11-25
- URL
TDQS
Scored across 87 tools
Descriptions go to extraordinary lengths to separate near-neighbor tools — well_list_connectors vs well_get_connector_coverage, well_show_records vs well_query_records, the six well_render_* cards, and the three statement-upload paths each carry explicit 'use X INSTEAD' cross-references. However, with 87 tools, several families (list_missing_invoices / preview_invoice_fetch / enqueue_invoice_fetch; get_accounting_settings / show_accounting_settings / upsert_accounting_settings; the four cash cards) still require reading long descriptions to select correctly, so some ambiguity remains for an agent holding the whole set.
All tools share the well_ prefix and follow a verb_noun snake_case pattern with semantically motivated verb choice: get_ for silent reads, list_ for enumerations, show_/render_ for cards, set_/update_/create_ for writes. Minor deviations exist — well_query_records vs well_get_schema, well_create_statement_upload vs well_upload_statement_bytes/content, and the get_/list_ split across similar domains (well_get_own_company vs well_list_periods) — but the pattern is predictable overall.
87 tools is far beyond the 25+ threshold the calibration marks as heavy, and even a genuinely broad accounting-platform scope does not fully justify it: an agent must hold boundaries for 28 distinct verbs and dozens of hyper-specific surfaces (six render tools, four statement-upload/claim tools, three missing-invoice reads). The count creates real selection cost even where each individual tool is well-scoped.
The surface is remarkably thorough for its domain: full lifecycle coverage for companies, people and invoices; categorization and posting worklists; connector management and retargeting; statement import (bytes, content, and claim flows); accounting settings with provenance; cash, burn, MRR, and runway computation; journal reposting; register diffs; and skill discovery. Minor gaps exist — no dedicated email/phone management for people (explicitly noted as unavailable), no manual transaction creation, no connector deletion — but none creates a dead end in the core close-books workflow.
Available Tools
87 toolswell_add_contact_channelAdd contact channelAInspect
Add a contact channel to a company or person.
Wraps the resource-scoped REST endpoints (POST /v1/{companies,people}/:id/{emails,phones,web-links,locations}).
channel + the matching value field:
email → value.email
phone → value.e164_number (E.164; a leading "+" is added if missing)
web_link → value.url (+ optional value.platform, default "website")
location → value.city, value.country (+ optional address_line1/2, region, postal_code) value.label is optional (defaults to "work").
NOTE: adding a phone is supported on a PERSON but NOT on a company (no endpoint) — that combination returns a clear error. To READ existing channels, use well_query_records on the parent (companies/people) or the channel root.
| Name | Required | Description | Default |
|---|---|---|---|
| value | Yes | Channel value — fill the field(s) for the chosen channel | |
| parent | Yes | Parent record type: company or person | |
| channel | Yes | Channel to add: email | phone | web_link | location | |
| parent_id | Yes | UUID of the parent company or person | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| parent | No | |
| channel | No | |
| success | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes beyond annotations by disclosing POST semantics, phone normalization (leading '+' added), defaults (label 'work', platform 'website'), and the error case for unsupported phone on company. No contradiction with readOnlyHint=false or destructiveHint=false.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded purpose, then endpoint, then a compact mapping list, and a note with edge cases. Each line adds value, no filler, well organized for scanning.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given 100% schema coverage, an output schema, and annotations already covering read-only/destructive hints, the description covers the essential usage rules, per-channel fields, defaults, and a key exclusion. Generic auth/rate-limit details are not needed here.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The channel-to-field mapping (email→email, phone→e164_number, web_link→url, location→city/country) is essential for correct invocation and goes well beyond the generic schema descriptions. It also clarifies optional fields and defaults.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: 'Add a contact channel to a company or person.' The REST endpoint reference and channel-type enumeration further distinguish it from read/remove siblings like well_query_records and well_remove_contact_channel.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly notes when not to use (phone on company) and provides an alternative for reading (well_query_records). This gives clear when/how vs. when-not guidance beyond the title.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_assign_accountAssign accountADestructiveInspect
Attach a bank account to a company, and say whether the workspace owns it.
Use this when an account carries no company, or when its ownership is still
unknown — the two states a figure that walks account ownership cannot be
computed over.
REQUIRED: account_id, plus at least one of company_id or ownership.
ownership is one of:
"workspace" — the business's own account
"counterparty" — someone else's, seen on an invoice or a payment
"unknown" — not yet classified
This changes figures, not just a label. An account marked "workspace" puts its transactions inside the internal-transfer rule: a movement with both legs on owned accounts stops counting as money leaving the business. Marking a counterparty's account as the workspace's own therefore removes real spend from the burn, quietly and consistently, with no error anywhere.
So do not guess it. An account's owner cannot be read off its name, its bank, or
the company that appears most often beside it. Ask, or leave it unknown —
"not yet classified" is a truthful state and a wrong classification is not.
company_id must name a company in the SAME workspace as the account; a
company from another workspace is refused rather than resolved. Pass
company_id: null to detach.
Returns { success: true, account_id, ownership, company_id } on success.
| Name | Required | Description | Default |
|---|---|---|---|
| ownership | No | Whether the workspace owns the account: "workspace", "counterparty", or "unknown". | |
| account_id | Yes | The UUID of the account to assign (required) | |
| company_id | No | The company that owns the account, in the same workspace. `null` detaches it. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| ownership | No | |
| account_id | No | |
| company_id | No | |
| company_name | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already mark this as destructive, and the description goes well beyond that by explaining exactly what changes: 'This changes figures, not just a label.' It details how marking an account as workspace affects the internal-transfer rule and can quietly remove real spend from burn, and it notes that cross-workspace companies are refused rather than resolved. 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is longer than average but well-structured: purpose first, then use case, then required-field rule, enum semantics, and a prominent warning in bold. Nearly every sentence earns its place, though the cautionary section could be tightened slightly without losing impact.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given that this is a write tool with cross-entity side effects, the description covers when to call it, prerequisites, enum semantics, constraints, error behavior, detach semantics, and the return shape. It is complete enough for an agent to invoke it correctly without needing additional context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, but the description adds critical semantics beyond it: the constraint 'account_id, plus at least one of company_id or ownership' is not in the schema's required list. It also explains the meaning of each ownership enum value and clarifies that passing company_id: null detaches, which adds real value over the schema descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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: 'Attach a bank account to a company, and say whether the workspace owns it.' This clearly distinguishes it from the sibling well_assign_missing_invoice_owners, which targets invoice ownership, not account ownership.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly says to use this tool when an account carries no company or when ownership is still unknown. It also warns against guessing and tells the user to leave ownership as 'unknown' instead. However, it does not name concrete sibling alternatives for other scenarios, so the when-not guidance is implicit rather than fully explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_assign_missing_invoice_ownersAssign missing-invoice ownersADestructiveInspect
Set the owner SET of the missing-invoice TRANSACTIONS you name — the only write for missing-invoice ownership.
REQUIRED: transaction_ids — the settled lines still missing a supplier invoice, from well_list_missing_invoice_owners. owner_person_ids — the people who together owe those invoices; pass an EMPTY array to clear the owners.
Ownership is per TRANSACTION and is a SET, not one owner and not a card rule. The write REPLACES the owner set on every named transaction: the people you send become its owners and anyone not sent is removed. Assigning several people to a (counterparty × month) gap creates ONE proof task per distinct person, and ONE supplier invoice resolves every owner's task for that gap — the fan-out is for accountability, not for N separate collections. Tell the user this plainly.
Each person_id must already be a member of the workspace (get them with well_query_records on people). A person outside the workspace is refused (refusal_reason NOT_FOUND), not silently dropped.
Closed periods are frozen: a transaction whose fiscal month already closed refuses the whole batch (refusal_reason CLOSE_OWNER_PERIOD_FROZEN) rather than rewriting a committed close. A transaction id the workspace does not own refuses the batch too (refusal_reason NOT_FOUND).
| Name | Required | Description | Default |
|---|---|---|---|
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. | |
| transaction_ids | Yes | The missing-invoice transactions to assign, from well_list_missing_invoice_owners. | |
| owner_person_ids | Yes | The workspace people who together own these transactions' missing invoices; an empty array clears the owners. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| owner_count | No | How many people own each of those transactions after the write. |
| refusal_reason | No | The WellError code when the write is refused — CLOSE_OWNER_PERIOD_FROZEN for a closed month, NOT_FOUND for a person outside the workspace or a transaction the workspace does not own. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| refusal_details | No | Structured facts a refusal code alone does not carry — for CLOSE_OWNER_PERIOD_FROZEN, the frozen `{ fiscalYear, fiscalPeriod }`. A NOT_FOUND names the offending id in `error` instead. |
| transaction_ids | No | The distinct transactions whose owner set the write replaced. |
| owner_person_ids | No | The explicit owner set written to every named transaction; empty when the owners were cleared. |
| transaction_count | No | How many distinct transactions the write touched. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the destructiveHint and readOnlyHint annotations, the description discloses that the write replaces the entire owner set, removes unspecified owners, creates one proof task per distinct person with a single resolving invoice, and refuses the whole batch with specific refusal reasons for closed periods, out-of-workspace people, and non-owned transaction ids. 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long, but it is front-loaded with purpose and required parameters, and every paragraph adds non-redundant behavioral or failure-mode information. A few stylistic redundancies (repeated NOT_FOUND reason, all-caps emphasis) keep it from being a 5.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive multi-parameter write tool with an output schema, the description covers prerequisites, set-replacement behavior, refusal semantics, frozen-period handling, and the get-people workflow. Optional workspace/conversation/idempotency semantics are fully handled in the schema, so no critical calling information is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema already describes all five parameters (100% coverage), so the baseline is 3; the description adds crucial semantics for transaction_ids and owner_person_ids: ownership is a SET, not a single owner or card rule, the write REPLACES the set, and the fan-out is for accountability rather than N independent collections. This materially improves parameter understanding beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific action ('Set the owner SET') on a specific resource ('missing-invoice TRANSACTIONS') and explicitly claims it is 'the only write for missing-invoice ownership,' distinguishing it from the read-only sibling well_list_missing_invoice_owners and other write tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit selection guidance: it is 'the only write for missing-invoice ownership,' names the source tool well_list_missing_invoice_owners for transaction_ids, instructs to get people via well_query_records, and explains the empty-array clearing behavior. This gives the agent clear when-to-use and prerequisite context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_claim_statement_draftClaim a statement dropped on the websiteAInspect
Claim every bank statement the user dropped on Well's website in one visit, using the ONE claim token from their message or from the /import-statement command argument.
The token covers every file in that drop: call this tool once per token, never per file. It works once and expires an hour after the drop.
Returns one document_id per file; poll well_get_statement_import_result for each.
A refused token (expired, already claimed, unknown) is final: tell the user plainly and ask them to attach the files here, never retry. Files listed under failed were claimed but did not ingest; ask for exactly those by hand.
| Name | Required | Description | Default |
|---|---|---|---|
| claim_token | Yes | The drop's claim token, exactly as written: from the argument after /import-statement or from the user's message. Single-use, covers every statement in that drop, expires one hour after the drop. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| failed | Yes | Files that were claimed but did not ingest. Their bytes are gone; ask the user for exactly these by name. |
| drop_id | No | The drop this token belonged to. Safe to quote; the token is not. |
| success | Yes | |
| documents | Yes | One entry per file that reached the import pipeline. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Despite annotations already marking readOnlyHint=false and destructiveHint=false, the description adds substantial behavioral context: single-use token, one-hour expiration, returns per-file document IDs, refused tokens are final, and failed files need manual re-request. 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is information-dense with no fluff. Each sentence adds value: token usage, polling, failure handling, and parameter nuances are all covered. It is front-loaded with the core concept and structured logically.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with four parameters, an output schema, and complex token lifecycle, the description covers every aspect an agent needs: when to call, how to handle results, failure modes, and parameter usage. Nothing critical is missing, and the output schema covers return structure.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 significant meaning: claim_token must be exactly as written, workspace_id has omission vs. requirement rules, conversation_id propagation details, and idempotency_key semantics. This far exceeds baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Claim'), resource ('every bank statement the user dropped on Well's website'), and scope ('in one visit'). It clearly distinguishes from siblings like well_upload_statement_bytes or well_get_statement_import_result, 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says to call once per token, never per file, and to poll well_get_statement_import_result for each result. It also gives clear when-not-to-use guidance (refused tokens are final, ask user to attach files, never retry), naming an alternative action and a sibling tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_create_companyCreate companyAInspect
Create a new company in the current workspace.
Use this tool when the user asks to create, add, or register a new company.
REQUIRED: name OPTIONAL: description
After creation, enrichment (logo, domain, industry, tax ID, description fill-in) runs asynchronously in the background. The new company is available immediately for follow-up actions, but enriched fields may take a few seconds to populate — re-query after a brief delay to see them.
Returns { success: true, company_id, name } on success, or { success: false, error } on failure.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Company name (required) | |
| description | No | Brief company description | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| name | No | |
| error | No | |
| success | Yes | |
| company_id | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses the async enrichment behavior (logo, domain, industry, etc.), immediate availability, and the need to re-query for enriched fields. It also specifies the return format. These go well beyond the annotations, which only indicate non-read-only and non-destructive nature.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three short paragraphs: purpose, required/optional, and behavioral notes. It is front-loaded with the purpose and is free of redundant phrasing. Every sentence adds value, making it concise and well-structured.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the essential context: what it does, when to use it, required vs optional params, async background processing, availability, and the return shape. It is complete for an agent to invoke the tool correctly, with schema handling the fine-grained parameter details.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has 100% parameter coverage with detailed descriptions, so the baseline is 3. The description only repeats the required/optional status of name and description, adding no new meaning beyond what the schema already provides. It does not elaborate on workspace_id, conversation_id, or idempotency_key, but the schema handles these adequately.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a clear verb+resource ('Create a new company') and explicitly ties it to user intent ('create, add, or register a new company'). It distinguishes itself from siblings like well_create_company_candidate by specifying the current workspace, though it does not name alternatives directly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit when-to-use guidance ('Use this tool when the user asks to create, add, or register a new company') and clarifies scope ('current workspace'). It does not explicitly mention when not to use it or name alternative tools, so it falls 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.
well_create_company_candidateCreate a company candidateAInspect
Mint a company candidate from a registry hit, the step between finding the company and creating its workspace. This is the deliberate pick the confirm-your-company card makes.
REQUIRED: registry_ref — the id of a well_search_company_registry hit. This tool hydrates that hit and mints the company as a primary (own-company) candidate. Then call well_create_company_workspace with the returned candidate_id to make it the company workspace.
Only a workspace owner or admin may mint a candidate. A caller without that role is refused, not silently ignored. When the picked company already has a confirmed company workspace, the result carries linked_to_existing_child: true and its workspace_id — switch into it with well_switch_workspace instead of creating another. Confirm the exact company with the user before calling; never pick one from a name alone.
| Name | Required | Description | Default |
|---|---|---|---|
| registry_ref | Yes | The registry ref an earlier well_search_company_registry hit carried as its `id`. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| candidate | Yes | The minted candidate, projected as well_get_own_company shows a candidate; null when linked to an existing child. |
| workspace_id | Yes | The existing company workspace when linked_to_existing_child is true; null otherwise. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
| linked_to_existing_child | Yes | True when the picked company already has a confirmed company workspace; its id is in workspace_id. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only indicate readOnlyHint=false, idempotentHint=false, destructiveHint=false. The description adds critical behavioral details: it requires workspace owner/admin role, a caller without it is refused (not silently ignored), the tool hydrates the registry hit, and it returns linked_to_existing_child when the company already has a workspace. This goes well beyond the annotations and fully discloses side effects and access requirements.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with a clear opening, a REQUIRED callout, and short paragraphs. Every sentence carries essential information: purpose, required parameter, role constraint, edge case, and user-confirmation instruction. There is no redundancy or filler, and key constraints are front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity—a write operation with role restrictions and an output schema—the description covers all critical aspects: the workflow position, required input, permission requirements, refusal behavior, the linked-to-existing-workspace edge case, and the next step. It does not need to explain the output schema because that is provided separately. The description is complete for an agent to call this tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 explaining that registry_ref is the id from well_search_company_registry and that the tool 'hydrates that hit', which clarifies the parameter's role in the flow. It also ties the returned candidate_id to the next tool. While the schema already documents each parameter, the description enriches the context of registry_ref and the sequence.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a precise verb and resource: 'Mint a company candidate from a registry hit', and places it in the workflow: 'the step between finding the company and creating its workspace.' It explicitly differentiates from the sibling well_create_company_workspace by naming the next step, and the phrase 'primary (own-company) candidate' distinguishes it from well_create_company. The purpose is unambiguous and distinct.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-to-use guidance: when you have a registry hit and need to mint a candidate. It names the exact sibling to call next (well_create_company_workspace) and provides an exclusion condition: if the result carries linked_to_existing_child, switch to well_switch_workspace instead. It also instructs to confirm the company with the user before calling. No ambiguity remains.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_create_company_workspaceCreate the company workspaceAIdempotentInspect
Create the company workspace from a candidate, the step that turns a picked company into a workspace the close runs in. This anchors the candidate's company as the new workspace's own company and links it to the membership it was created under.
REQUIRED: candidate_id — from well_create_company_candidate. This mints the child workspace, projects its accounting settings from the country defaults, anchors its own company, and writes the lineage row, so well_switch_workspace can move into it in the same conversation. Idempotent: calling it again on the same candidate returns the same child, with already_anchored true.
Only a workspace owner or admin may create the company workspace. A caller without that role is refused, not silently ignored. Confirm the company with the user before calling.
| Name | Required | Description | Default |
|---|---|---|---|
| candidate_id | Yes | The candidate id returned by well_create_company_candidate. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| name | No | The company workspace's name, derived from the candidate. |
| error | No | |
| success | Yes | |
| workspace_id | No | The created (or existing) company workspace's id. |
| own_company_id | No | The public id of the company anchored as the workspace's own. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| already_anchored | No | True when this call did not mint a fresh workspace: the candidate had already minted its child (a converged replay) or resolved to a company already anchored on the caller. The same child is returned either way. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=false, idempotentHint=true, and destructiveHint=false. The description adds rich behavioral context: it explains the sequence of internal actions (mints workspace, projects accounting settings, anchors own company, writes lineage), and discloses that non-owners/admins are refused rather than ignored. It also reinforces idempotency by stating repeated calls return the same child with already_anchored true. No contradictions 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured: purpose, required parameter, behavior, permissions, and user confirmation. Each sentence earns its place, and the key prerequisite and idempotency are front-loaded. The line breaks improve scannability. It is detailed yet not verbose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the complexity of a multi-step workspace creation, the description covers all essential operational aspects: what it does, prerequisites, sequence, permissions, idempotency, and confirmation. An output schema exists, so return format need not be described. It also references the relevant sibling tool for switching workspace, making it self-sufficient for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 context for candidate_id (explicitly says it comes from well_create_company_candidate) and explains idempotency behavior that maps to idempotency_key. However, it does not elaborate on workspace_id or conversation_id beyond the schema. It adds marginal value but does not significantly compensate for any gap since the schema already covers everything.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: 'Create the company workspace from a candidate, the step that turns a picked company into a workspace the close runs in.' It clearly differentiates from well_create_company_candidate by naming it as the prerequisite and from well_switch_workspace as a follow-up. An agent can tell exactly what this tool does 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit usage guidance: requires candidate_id from well_create_company_candidate, describes the flow (mints workspace, anchors company, writes lineage) and mentions the follow-up well_switch_workspace. It also states permission requirements and instructs to confirm with the user before calling. This is explicit when-to-use and even what to avoid (silent refusal).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_create_invoice_documentCreate invoice documentAInspect
Render an existing invoice as a print-ready PDF and attach it as the invoice's source document.
The letterhead carries the issuing company's own mark when Well has one on file, and otherwise sets the issuer's name as text. Never promise a logo.
Use this tool when the user asks to generate, render, or attach a PDF for an invoice that already exists in the workspace. This does NOT email or send the invoice anywhere — it only creates and attaches the file.
REQUIRED: invoice_id (the invoice must already exist)
Refused if the invoice is already linked to a REAL ingested document (an upload, a connector import, or a provider-issued PDF) — that source of truth is never overwritten.
Returns { success: true, invoice_id, document_id, reference_number, file } on success, or { success: false, error } on failure.
file carries the rendered PDF's name and size plus the links to fetch it:
download_url (saves the file), signed_url (opens it), and app_url (the
document in Well). Hand the user download_url when they ask for the PDF
itself. Both signed links stop working at expires_at; app_url does not.
| Name | Required | Description | Default |
|---|---|---|---|
| invoice_id | Yes | ||
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| file | No | |
| error | No | |
| success | Yes | |
| invoice_id | No | |
| document_id | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| reference_number | No | |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only state readOnlyHint=false and destructiveHint=false; the description goes well beyond by disclosing that the tool refuses to overwrite a REAL ingested document, that it conditionally uses a logo or issuer name as text, and that 'Never promise a logo.' It also explains link expiry semantics for signed URLs versus app_url. This is strong behavioral transparency beyond 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but organized into clear functional blocks: the action, the letterhead caveat, usage guidance, refusal condition, and return-value explanation. It front-loads the core purpose in the first sentence, and the later detail about URLs and expiry is genuinely useful rather than filler. Minor inefficiency exists because some return-shape details overlap with the output schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
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 output schema, and the annotations, the description covers all essential operational context: prerequisites (invoice must exist), refusal conditions (already linked to a REAL document), side-effect expectations (does not send), return shape, and link semantics. An agent has enough information 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.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 75%, and the schema already thoroughly documents workspace_id, conversation_id, and idempotency_key. The description adds the key precondition that invoice_id must reference an existing invoice, but it does not add much semantic value beyond the schema for the optional parameters. This is adequate but not exceptional.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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: 'Render an existing invoice as a print-ready PDF and attach it as the invoice's source document.' This clearly distinguishes the tool from siblings like well_create_invoice_from_data (creates an invoice from data) and well_upload_document (uploads arbitrary documents), and the first sentence alone tells the agent exactly 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives an explicit usage trigger: 'Use this tool when the user asks to generate, render, or attach a PDF for an invoice that already exists in the workspace.' It also states a clear when-not behavior: 'This does NOT email or send the invoice anywhere.' It stops short of naming alternative sibling tools explicitly, so it has clear context and exclusions but not full alternative routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_create_invoice_from_dataCreate invoice from dataAInspect
Create an invoice in Well from data you extracted by reading an invoice (your own OCR) — you send the structured fields, not the file.
Well persists the invoice + its line items + payment means using the same pipeline as uploaded documents. Fill every field you can read from the document:
issuer / receiver: { name (required), company_id?, domain?, tax_id? }
reference_number, issue_date (YYYY-MM-DD), due_date? (YYYY-MM-DD), currency (ISO 4217)
totals?: { items_total?, tax_total?, grand_total }
line_items[]: { name, quantity?, unit_price, currency?, tax_rate? }
payment_means?[]: { type, iban?, bic?, scheme? }
status?: draft | issued | paid | canceled
ONE CALL IS THE WHOLE WRITE. This tool takes the invoice's status and both parties' company ids, so a create never needs a well_update_invoice after it:
The user asked to DRAFT an invoice → pass status: "draft" here.
You already found the company (well_query_records, well_get_entity) → pass its company_id on that party. Naming the party without its id re-resolves it, which can attach the invoice to the wrong company or create a duplicate one.
Creating and then patching the same invoice writes twice and shows the user two confirmations for one action. Put the intent in this call.
| Name | Required | Description | Default |
|---|---|---|---|
| issuer | Yes | ||
| status | No | The invoice's lifecycle status. Set it here when the user asked for one ("draft an invoice") — do NOT create and then call well_update_invoice to change it. Omitted, the status is derived from the document type. | |
| totals | No | ||
| currency | Yes | ISO 4217 (3 letters). | |
| due_date | No | ISO 8601 YYYY-MM-DD. | |
| receiver | Yes | ||
| issue_date | Yes | ISO 8601 YYYY-MM-DD. | |
| line_items | Yes | ||
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| payment_means | No | ||
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. | |
| reference_number | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| invoice_id | No | |
| document_id | No | |
| payment_means | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| reference_number | No | |
| invoice_item_count | No | |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already indicate destructiveHint=false and readOnlyHint=false, so the description doesn't need to repeat those. It adds important behavioral details: that one call is the whole write, that re-resolving without company_id can attach to wrong company or create duplicates, and that patching after create causes two confirmations. It does not mention idempotency_key but that is in the schema. Slight deduction for not mentioning the persistence pipeline in detail, but it's clear enough.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with clear sections and bullet points, but it is quite long. It front-loads the core purpose and then provides detailed usage guidance. While lengthy, every sentence adds value, and the structure helps readability. Minor deduction for length, but it's justified by the complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the tool's purpose, usage, key parameters, and integration with sibling tools. It explains edge cases like re-resolution and workspace selection. The output schema exists, so return values need not be described. For a complex write tool, this is remarkably complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 54%, and the description compensates by explaining key parameters like company_id (always send when known), status (set it here not via update), and workspace_id (omit when single workspace). It also explains line_items, totals, and payment_means structure. However, some parameters like reference_number, issue_date, and currency are only described in schema, but the description covers the important ones.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool creates an invoice from structured data extracted via OCR, not from a file. It lists the fields to be filled and explicitly differentiates from sibling tools like well_create_invoice_document (which likely handles file uploads) and well_update_invoice (which is for patching).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says to use this tool for the whole write operation, when to pass a status (when user asked for draft), when to pass company_id (when already found), and warns against creating and then patching. It also tells when to omit workspace_id based on token authorization. This is exemplary guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_create_personCreate personAInspect
Create a new person (contact) in the current workspace.
Use this tool when the user asks to add, create, or register a new contact, employee, or person.
REQUIRED: first_name OPTIONAL: last_name, job_title
After creation, enrichment runs asynchronously in the background.
Returns { success: true, person_id, full_name } on success, or { success: false, error } on failure.
| Name | Required | Description | Default |
|---|---|---|---|
| No | |||
| phone | No | ||
| job_title | No | Job title | |
| last_name | No | Last name (optional) | |
| first_name | Yes | First name (required) | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| full_name | No | |
| person_id | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already indicate readOnlyHint=false and destructiveHint=false, so the description correctly implies a write operation. It adds valuable context beyond annotations by noting that 'enrichment runs asynchronously in the background' and describing the success/failure return shape. This gives the agent awareness of post-creation side effects without contradicting 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact—roughly five sentences—and front-loaded with the core purpose, followed by usage, required/optional fields, side effects, and return value. Every sentence earns its place, with no filler or redundant restatement of the title.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the 8-parameter schema where most parameters have rich descriptions, and the presence of an output schema, the description covers the essentials: purpose, usage, required/optional fields, and asynchronous enrichment. It does not restate return details (covered by output schema) nor parameter semantics (covered by schema descriptions). The only minor gap is not explicitly mentioning the special workspace_id/conversation_id behavior, but those are fully documented in the schema, so the description stays sufficiently complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 75%, with detailed descriptions for workspace_id, conversation_id, idempotency_key, and the name fields. The description highlights first_name as required and last_name/job_title as optional, but this is already in the schema. It does not add meaning for email, phone, workspace_id, conversation_id, or idempotency_key, leaving the schema to carry most of the parameter documentation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific action and object: 'Create a new person (contact) in the current workspace.' It also provides common user intents ('add, create, or register') and clarifies the resource as a person versus other entities like companies. The verb 'Create' is unambiguous and distinct from sibling create tools like well_create_company.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly states 'Use this tool when the user asks to add, create, or register a new contact, employee, or person,' which gives clear context for when to select it. However, it does not explicitly contrast with alternatives like well_add_contact_channel (for adding channels to an existing person) or well_update_person, so it relies on implied exclusions rather than naming them.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_create_statement_uploadCreate statement upload slotAInspect
Mint a one-time, short-lived upload slot for a bank-statement file.
Use this when the user has a statement file (PDF, or a large CSV/XML) to import; the file's bytes do not travel through the model. It returns a single-use upload URL + token; the client (or the user) POSTs the raw file bytes to that URL, and the resulting document enters the exact same import pipeline as an in-app upload (detection, dedup, promotion).
This result renders a card in widget-capable hosts right away — do not wait for a poll to make it appear. One statement file per call: mint a separate slot for each file. Once the client has uploaded the file bytes, call well_get_statement_import_result with the document_id below one time to learn the outcome. The card polls the import result itself until it settles, so call that tool again only if the user asks.
The token authorizes exactly ONE upload to this workspace and expires in 15 minutes. It is burned on first use — a second upload needs a new slot. It cannot be used for anything other than a statement upload.
The response's document_id is PRE-ALLOCATED at mint time — the upload has not happened yet, and this exact id is what the document will carry once it does. A call to well_get_statement_import_result before the upload lands is a normal "not_found_yet", not an error.
| Name | Required | Description | Default |
|---|---|---|---|
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| token | No | |
| success | Yes | |
| upload_url | No | |
| document_id | No | The pre-allocated document id — poll well_get_statement_import_result with it. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| expires_in_seconds | No | |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only mark non-read-only, non-destructive, non-open-world. The description adds critical behavioral details: token expiration (15 min), one-time burn, pre-allocated document_id, and that a premature result call returns 'not_found_yet' rather than an error. This goes well beyond the annotations and gives an agent a clear model of what happens.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is longer than average but every sentence earns its place. It's front-loaded with the core purpose, then flows logically through usage, token behavior, and response semantics. No filler, and the structure mirrors the criticality of the details.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with this complexity (mint, upload, poll), the description is fully sufficient. It covers the upload flow, token constraints, pre-allocation, card behavior, and how to follow up. Nothing an agent needs to invoke and use it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% — all three parameters have rich descriptions covering workspace_id, conversation_id, and idempotency_key. The description adds little to these, only indirectly referencing workspace by mentioning token authorization. Per the baseline for full schema coverage, a 3 is appropriate; the schema already carries the semantic weight.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a concrete action ('Mint a one-time, short-lived upload slot for a bank-statement file'), clearly identifies the target resource, and immediately distinguishes it from direct-upload siblings by stating that file bytes do not travel through the model. It's specific and not a tautology.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use this tool ('when the user has a statement file... to import') and gives precise follow-up instructions (call well_get_statement_import_result once after upload, card polls itself, only call again if user asks). It also clarifies the one-file-per-call rule and token lifecycle, 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.
well_delete_companyDelete companyADestructiveInspect
Delete a company from the current workspace (soft delete).
Use this tool when the user asks to delete, remove, or archive a company.
REQUIRED: company_id
This soft-deletes the company and its company_person relationships. Linked people records themselves are NOT deleted. Invoices and documents referencing the company are preserved.
Returns { success: true, company_id } on success, or { success: false, error } on failure.
| Name | Required | Description | Default |
|---|---|---|---|
| company_id | Yes | The UUID of the company to delete (required) | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| company_id | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark destructiveHint=true and readOnlyHint=false, but the description goes far beyond by detailing the soft-delete semantics, what is deleted (company and company_person relationships), what is preserved (people, invoices, documents), 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose and usage trigger, followed by behavior and return values. It is concise, though the 'REQUIRED: company_id' line is slightly redundant given the schema's required list. Overall efficient and well-structured.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the destructive action, the description covers what happens to related data, what is preserved, and the success/failure return format. The output schema exists, parameters are fully described in the schema, and annotations carry the safety profile. 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.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, with all four parameters (company_id, workspace_id, conversation_id, idempotency_key) well-documented in the schema. The description adds no new parameter-level meaning beyond restating company_id as required, which 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.
Does the description clearly state what the tool does and how it differs from similar tools?
States exactly what it does: 'Delete a company from the current workspace (soft delete).' The verb 'delete', resource 'company', scope 'current workspace', and the soft-delete qualifier differentiate it from sibling delete tools like well_delete_invoice and well_delete_person.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit triggers: 'Use this tool when the user asks to delete, remove, or archive a company.' It clearly identifies the intended context and required parameter, though it does not explicitly name alternatives or exclusion cases. Clear context without exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_delete_invoiceDelete invoiceADestructiveInspect
Delete an invoice from Well (soft delete).
REQUIRED: invoice_id
Soft-deletes the invoice. Linked line items and payment_means rows are NOT cascade-deleted — they remain in the database, orphaned. The delete is reversible only at the database level.
| Name | Required | Description | Default |
|---|---|---|---|
| invoice_id | Yes | The UUID of the invoice to delete | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| invoice_id | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds significant behavioral detail beyond the annotations: it explicitly says 'soft delete', explains that linked line items and payment_means rows are NOT cascade-deleted and remain orphaned, and notes the deletion is reversible only at the database level. This goes beyond the destructiveHint=true annotation, which only signals destructiveness. It does not contradict the annotations and provides valuable side-effect information.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise and front-loaded with the core purpose and the required parameter. The body consists of two sentences that efficiently cover the soft-delete behavior and its side effects. The 'REQUIRED: invoice_id' line is a small redundancy with the schema but serves as a useful reminder. Overall, it is well-structured and wastes no words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive tool with 4 parameters and an output schema, the description covers the essential behavioral caveats: soft delete, non-cascade, and reversibility constraints. It does not need to explain return values because an output schema exists. The parameter semantics are fully covered by the schema, and the description provides the missing operational context (orphaned rows). It is adequately complete for agent invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 only restates 'REQUIRED: invoice_id', which the schema already documents as required. It does not add any additional meaning for workspace_id, conversation_id, or idempotency_key beyond what the schema provides. No new parameter semantics are introduced.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action ('Delete an invoice from Well') and identifies the resource (invoice), while also specifying 'soft delete' to set it apart from a hard delete. This distinguishes it from sibling delete tools like well_delete_company and well_delete_person. The purpose is unambiguous and directly tied to the tool name.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit guidance on when to use this tool versus alternatives, such as well_update_invoice or well_delete_company. The description does not state prerequisites or scenarios where this tool is preferred. The only contextual hint is the resource name, leaving the agent to infer usage without direct guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_delete_personDelete personADestructiveInspect
Delete a person (contact) from the current workspace (soft delete).
Use this tool when the user asks to delete, remove, or archive a contact.
REQUIRED: person_id
This soft-deletes the person and its company_person relationships. Linked companies themselves are NOT deleted. The authenticated user cannot delete their own person record.
Returns { success: true, person_id } on success, or { success: false, error } on failure.
| Name | Required | Description | Default |
|---|---|---|---|
| person_id | Yes | The UUID of the person to delete (required) | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| person_id | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only mark destructiveHint=true, and the description adds substantial behavioral context beyond that: soft-delete semantics, deletion of company_person relationships only, linked companies NOT being deleted, the self-delete restriction, and the exact success/failure return shapes. This is exactly the kind of context an agent needs for a destructive operation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is tightly structured and front-loaded, with the core action and trigger condition first, followed by key constraints and the return contract. Every sentence contributes useful information, and the return format is clearly specified without excess.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive tool with rich schema descriptions for all four parameters and an output shape already stated in the description, nothing essential is missing. The description covers side effects, restrictions, and error handling, making it fully actionable for an agent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 person_id, workspace_id, conversation_id, and idempotency_key in detail. The description restates person_id as required and adds deletion-related behavior, but it does not add new parameter semantics 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.
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 person (contact) from the current workspace') and distinguishes the tool by adding 'soft delete' plus the scope of what is and is not deleted. This differentiates it clearly from sibling tools like well_delete_company and well_delete_invoice.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly tells an agent when to use it: 'Use this tool when the user asks to delete, remove, or archive a contact.' However, it does not mention when not to use it or name alternatives, such as removing only a contact channel via a sibling tool, 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.
well_enqueue_invoice_fetchQueue invoice collectionAIdempotentInspect
Queue invoice collection for named counterparties. This creates one durable backlog task per counterparty. The browser agent (a provider that carries a blueprint or a real portal URL) or the manual-upload route picks up each task later.
Call this tool only after the user explicitly confirms the launch. Never call it on your own initiative.
Get counterparty_company_ids from well_list_missing_invoices. This tool takes no period argument: a collection task belongs to a counterparty, not a month.
A repeat call for a counterparty that already has a non-terminal task reuses that task (already_active: true) instead of creating a second one.
provider.has_blueprint and provider.has_portal_url on an enqueued row state which counterparties a browser agent will visit (either one is enough), and which fall back to manual upload (neither).
Creating the tasks launches nothing in the browser. Inside Well the tasks page and the chat card start and track them. From outside Well, hand the user the collect_url from well_preview_invoice_fetch to start the runs. That link never covers every enqueued counterparty. One link names at most 25 portals, so a counterparty past that ceiling appears in well_preview_invoice_fetch's collect_url_omits instead of on the link. A counterparty with provider: null has no portal at all and is routed to manual upload. A counterparty with an address the link cannot carry appears in collect_url_unaddressable. Never tell the user the link covers a counterparty it does not name.
Use well_preview_invoice_fetch first to see what a fetch would cover — it is read-only and launches nothing. Use well_enqueue_close_invoice_fetch instead of this tool when you are inside a close run: it is the same action, scoped to that run's flow_run_id. Use this tool outside a close run.
Report the counts back to the user: how many tasks were enqueued, how many of those were already active, and how many counterparties were skipped, with each skip's reason.
| Name | Required | Description | Default |
|---|---|---|---|
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. | |
| counterparty_company_ids | Yes | Counterparty companies to queue collection for, from well_list_missing_invoices. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| skipped | No | |
| success | Yes | |
| enqueued | No | |
| skipped_count | No | |
| enqueued_count | No | Rows in enqueued — created or reused. |
| refusal_reason | No | The WellError code when the write is refused. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| already_active_count | No | Of enqueued_count, how many reused an existing task rather than creating one. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations, the description discloses idempotent reuse via already_active, that no browser actions are launched synchronously, the 25-portal collect_url ceiling, provider: null fallback to manual upload, and omitted/unaddressable counterparty handling. These are behavioral traits an agent would not infer 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is lengthy but every sentence carries necessary behavioral or routing information. It front-loads the core purpose and follows with usage constraints, then edge cases taxonomically (already active, portal ceiling, null provider, unaddressable). No filler or repetition is present.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity and the presence of an output schema, the description covers everything an agent needs: safety gate, input source, idempotency, asynchronous behavior, inside/outside close-run routing, link limitations, manual-upload fallback, and required reporting to the user. Nothing material is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is already high. The description adds meaningful parameter guidance by naming the source of counterparty_company_ids (well_list_missing_invoices) and explicitly noting the tool takes no period argument, which prevents a common misuse. It does not add much beyond schema for workspace_id, conversation_id, or idempotency_key, but those are already well described in the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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: 'Queue invoice collection for named counterparties,' and adds a precise mechanism: 'one durable backlog task per counterparty.' It clearly distinguishes this tool from the close-run variant, well_enqueue_close_invoice_fetch, and positions it against well_preview_invoice_fetch.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description states exactly when to invoke the tool ('only after the user explicitly confirms the launch'), never on own initiative, and where the input comes from ('Get counterparty_company_ids from well_list_missing_invoices'). It explicitly routes to well_preview_invoice_fetch for read-only preview and to well_enqueue_close_invoice_fetch inside a close run, leaving no ambiguity about alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_get_accounting_settingsGet accounting settingsARead-onlyInspect
Read the workspace's accounting settings and their provenance WITHOUT showing the user anything: country of incorporation, incorporation date, tax ID, fiscal year start, base currency, and accounting framework. Each field carries its value, where the value came from, and the stored "Suggested" fills. This draws nothing on the user's screen and asks for no confirmation.
Use it ONLY for a silent CHECK the model acts on itself: the close-books step deciding whether the fiscal year start and the base currency are already present and trusted before it moves on, a step that needs the current framework or start month to compute something. Read the fields and act in the same turn — there is no card and no click to wait on.
⚠️ To have the USER review or CONFIRM the settings, call well_show_accounting_settings INSTEAD — that one draws the card the user completes and confirms. This tool cannot draw one, so a confirm step run here leaves the user with nothing to act on.
| Name | Required | Description | Default |
|---|---|---|---|
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| settings | No | |
| next_step | No | What to do with the card this result renders. Added by the dispatcher when the card asks for a click. |
| suggestions | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Even though annotations already declare readOnlyHint=true and destructiveHint=false, the description adds meaningful behavioral context beyond the annotations: it draws nothing on screen, asks for no confirmation, reports provenance and stored 'Suggested' fills, and cannot draw a confirmation card. This is exactly the kind of behavioral disclosure an agent needs to invoke the tool correctly.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is somewhat long but every sentence carries useful decision-relevant information. The core behavior is front-loaded in the first sentence, and the warning about the confirmation-flow alternative is clearly separated. Minor redundancy between 'WITHOUT showing the user anything' and 'draws nothing on the user's screen' keeps it from a 5.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only settings tool, the description is complete: it states what is returned conceptually, explicitly clarifies the silent/no-UI behavior, gives conditional usage guidance, and distinguishes it from the sibling show tool. The output schema covers return-value details, so nothing essential is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 `workspace_id` and `conversation_id` thoroughly. The description does not add significant parameter-specific semantics beyond the schema, which merits the baseline score of 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Read'), resource ('the workspace's accounting settings and their provenance'), and importantly differentiates this from the sibling `well_show_accounting_settings` by emphasizing it does not draw anything on the user's screen. An agent can unambiguously identify what this tool does and what it does not do.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage guidance is explicit: use this tool only for a silent CHECK the model acts on itself, and read fields and act in the same turn. It names the alternative `well_show_accounting_settings` for when the user must review or confirm, and explains why using this tool for a confirm step would leave the user with nothing to act on.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_get_connector_coverageGet connector coverageARead-onlyInspect
Read what a workspace has CONNECTED and what it can connect. This draws nothing on the user's screen.
Use it for every coverage CHECK: a data skill confirming a bank is connected before it measures anything, a step that needs a workspace_connector_id, a health read on a connector the user asked about. Read each row's state and hand the answer back in your own words, in the same turn — there is no card to wait on here, and no acknowledgement to ask for.
⚠️ FOR A CONNECT STEP, CALL well_list_connectors INSTEAD. Same scope arguments, same rows, and its result draws the card with the install links and the Continue the user clicks. This tool cannot draw one, so a connect step run here leaves the user with prose and no way to act.
Do NOT read workspace_connectors records to work out connection coverage; this tool is that answer.
The rows are the rows of well_list_connectors, field for field. Its description carries the field reference, and this description does not repeat it.
| Name | Required | Description | Default |
|---|---|---|---|
| q | No | Name search across the full catalog (e.g. a specific bank). Omit for the curated, matched-first view. | |
| kind | No | Scope the card server-side. Three financial domains: "bank" (every bank and neobank, including the long tail of open-banking institutions, plus the platforms that hold an account like Qonto and Pennylane — never a payroll or billing tool that merely reports transactions), "accounting", and "invoicing". Plus two scopes the server resolves from display categories rather than from a financial domain: "upload_surface", the places invoices ARRIVE (mailboxes, messaging apps, file drives), and "storage", the drives Well FILES INTO (Google Drive, Dropbox, OneDrive). The last two are opposite directions on the same drives, so a workspace can hold both connections and each is offered on its own card. Use this for a connect-a-bank, a connect-where-invoices-arrive or a connect-where-Well-files step instead of filtering the default view yourself. Omit for every connectable connector. | |
| limit | No | Max connectors to return (1-100, default 50). | |
| offset | No | Number of connectors to skip, for paging (default 0). | |
| country | No | The company's country as an ISO 3166-1 alpha-2 code (e.g. "FR"). When set, the connectors that serve that country sort first, then the ones that serve its region, then the rest — the order only, no row is dropped. Use it on a connect-a-bank step so the banks that fit the company appear first; take it from the workspace identity's country. Omit when the country is unknown, and the order is left unchanged. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| from_selection | No | Scope the card to the connectors behind the counterparties the user picked on the missing-invoices card in this conversation, each installable one pre-checked. Use it for the connect step that FOLLOWS a vendor pick. It returns those vendors' connectors and NOTHING else: a picked connector that brings no invoices in is dropped, and no accounting or invoicing tool the user did not pick is offered here: that offer is its own step, scoped with `kind`. Cannot be combined with `q` or `kind`: those name a catalog to browse, and this names a set already decided. Returns an empty list when this conversation holds no pick for this workspace, or when no picked counterparty matched a connector. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| include_unsent_counts | No | Add the top-level `unsent_document_counts`: every tool Well forwards this workspace's documents to, each with how many documents it has not received yet. That array is the whole answer — it holds every one of the workspace's outbound connections, whatever catalog page was requested, and the rows carry no count at all. Off by default, because it costs an extra read. The figure is the workspace's whole backlog, not a period's. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| limit | No | The page size that was REQUESTED. The catalog may return fewer. |
| scope | No | What this result is a list OF: the requested kind, the picked vendors' connectors, or the curated catalog when neither was asked for. The card words itself from this, so it never describes the rows as a domain that was not requested. |
| total | No | Every connector matching the query, across all pages — NOT the length of `connectors`. |
| offset | No | How many catalog rows this page skipped. |
| success | Yes | |
| row_count | No | How many rows this result puts ON THE CARD, prepended rows included. `0` on a `picked_vendors` scope means the pick has no connector behind it that can bring an invoice in: the card carries nothing to tick, so say so in half a sentence and move on. Not a paging cursor — `page_count` is. |
| connectors | No | |
| page_count | No | The catalog page's own length, and the ONLY safe paging cursor: advance by `offset + page_count`. `connectors` can be LONGER — on the first page of an unsearched browse the workspace's already-connected rows are prepended so they cannot be lost to the catalog's ordering — so paging by the array's length silently skips exactly that many catalog rows on every later request. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| install_all_url | Yes | One link that installs every installable connector in this result. Null when the result offers nothing to install, or when its scope names a set the reader has not chosen. When it is non-null it is the ONLY install link the answer offers — do not list the rows' own install_url beside it. When it is null, the rows' own install_url is the offer instead. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| install_all_omitted | Yes | The service ids install_all_url could not carry, because one link names a bounded number of connectors. Offer these rows their own install_url instead of promising the batch link covers them. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
| unsent_document_counts | No | Every tool this workspace forwards documents to, each with the documents it has not received yet, biggest backlog first. Present only when `include_unsent_counts` was set. THIS IS THE ONLY PLACE THE BACKLOG IS REPORTED: it is not a page, it carries the workspace's whole set of outbound connections whether or not the requested catalog page holds their rows, and the catalog rows carry no count. An empty array means the workspace forwards to nothing; an absent field means nothing was measured. `unsent_document_count_is_upper_bound: true` means a document filter applies to that connection, so the count is a maximum and reads as `up to <n>`. Every count is read off Well's own forward record, so it measures what Well wrote down rather than what the tool confirmed. Name a tool by its `name`. An entry reading `0` is a tool that is up to date: say nothing about it. |
| picked_vendors_filtered | No | How many of the picked vendors' connectors were dropped for bringing no invoices in. Present on the `picked_vendors` scope only. Above zero means the card is SHORTER than the pick: say that those vendors' tools cannot deliver an invoice, rather than letting the gap read as a pick the user never made. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already mark the tool as read-only and non-destructive, and the description goes beyond them by revealing that it draws nothing on screen, returns no card, and must be answered in the same turn. It also discloses a critical limitation: this tool cannot render a connect card, which materially changes how an agent should use it.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but every sentence earns its place: purpose is front-loaded, usage guidance is concrete, and the sibling-tool warning is prominent. The final paragraph deliberately avoids duplicating the field reference by pointing to well_list_connectors, keeping the description focused.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with nine parameters and an output schema, the description covers the essential selection and invocation context: what this tool reports, when to call it, when to call the sibling instead, and the UI behavior the agent must account for. Since the schema fully documents parameters and an output schema exists, nothing critical is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has 100% description coverage and individually documents all nine parameters in detail, so the description does not need to repeat parameter semantics. The description adds only a cross-reference to well_list_connectors for field-level definitions and mentions 'same scope arguments,' which is useful routing context but not new parameter meaning.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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 what a workspace has CONNECTED and what it can connect,' which is far more precise than the title alone. It also explicitly differentiates this tool from well_list_connectors by saying the connect step must call the sibling instead, and clarifies this tool is the answer for coverage checks.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-to-use cases: every coverage check, a data skill confirming a bank is connected, a step needing a workspace_connector_id, and a health read on a connector. It also gives explicit when-not-to-use guidance, including a direct instruction to call well_list_connectors for connect steps and a warning not to read workspace_connectors records manually.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_get_customer_einvoicing_detailsGet customer e-invoicing detailsARead-onlyInspect
Get one CUSTOMER's e-invoicing identity — the registry values an invoice to that customer is routed on, or that a period aggregate for it is reported under.
Use this when the question is about the party the workspace BILLS: "can we invoice this customer electronically", "what is their SIREN / VAT number / billing address", "what do we still need before we can route this invoice". Read well_get_own_company instead when the question is about which company the workspace ITSELF is.
Returns customer_kind ("company" routes an invoice, "individual" reports a sale, absent when the customer's type is not stated), customer (the composite: company_id, name, subline, identified), fields (one entry per detail the graph can hold, each with its value and provenance when one is held), unstorable_fields (details this flow needs that no column holds yet), hints and connectors_url.
A field listed in unstorable_fields is not a gap the user can close — say plainly that Well cannot store it yet, and never ask for it. A fields entry with no value IS answerable and is what still blocks the route.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| company_id | Yes | The customer's public company UUID (the `company_id` field on a companies record). | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| hints | No | |
| fields | Yes | |
| success | Yes | |
| customer | Yes | |
| error_reason | No | |
| customer_kind | No | |
| connectors_url | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| unstorable_fields | Yes | |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark the read as safe; the description adds meaningful behavioral context beyond that: the meaning of absent customer_kind, the semantics of fields entries with no value, that unstorable_fields are not user-closable gaps, and that the read will not guess a workspace when multiple are authorized. 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but densely structured: purpose, usage boundary, return shape, edge-case semantics, and workspace guidance each earn their place. It is front-loaded with the core identity statement. Slightly more verbose than strictly necessary, but every sentence adds actionable information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only single-resource tool with an output schema and rich parameter schema, the description is complete. It explains how to interpret ambiguous return states, when to call directly, when to pass workspace_id, and how to distinguish from the relevant sibling. 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.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents company_id, workspace_id, and conversation_id fully. The description adds some parameter-level context, especially the workspace_id rule for multi-workspace tokens, but most of its added value concerns output interpretation rather than parameter meaning.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a precise verb and resource: 'Get one CUSTOMER's e-invoicing identity' and defines the object as the registry values an invoice is routed on or a period aggregate is reported under. It also differentiates itself from well_get_own_company by scoping to the party the workspace bills, so an agent can distinguish it 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use examples ('can we invoice this customer electronically', 'what is their SIREN / VAT number') and explicitly routes the alternative case to well_get_own_company. It also provides conditional call guidance for single-workspace versus multi-workspace tokens, 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.
well_get_design_tokensGet design tokensARead-onlyInspect
Get Well's colours, shape and type vocabulary, so a view you compose for Well data looks like Well rather than a generic page.
Call this ONLY when you are about to render something yourself — an HTML artifact, a report, a chart you are drawing. You do not need it to answer in prose or in a markdown table.
Do NOT use it to restyle a card a Well tool already drew. Where a tool ships its own card the host renders it, and a second styled copy of the same figures is a duplicate, not an improvement.
Returns colors (roles, not raw token names — page_background, card_surface, text_primary, accent, positive, negative, ...), series (categorical chart colours in the order to consume them), shape (corner radius and gap), fonts, and color_scheme, which tells you which ground to compose against. When it is absent the stylesheet did not declare one — pick a ground from page_background rather than assuming.
Values come from the same token package the Well app, the browser extension and the tool cards compile against, so they cannot drift from the product.
| Name | Required | Description | Default |
|---|---|---|---|
| workspace_id | No | Target workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| fonts | Yes | |
| hints | No | |
| shape | Yes | |
| colors | Yes | |
| series | Yes | |
| success | Yes | |
| color_scheme | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and destructiveHint=false, so safety is covered. The description adds valuable behavioral context: it describes the return object's structure (colors, series, shape, fonts, color_scheme), explains how to interpret the absence of color_scheme, and notes that values come from the token package and cannot drift. It also clarifies that omitting workspace_id returns the same answer, which is a behavioral nuance beyond the schema. No contradictions 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is moderately long but every sentence earns its place. It front-loads the core purpose and then delivers usage constraints, return-value semantics, and a final note on provenance. While it could be trimmed slightly (e.g., the last sentence on token drift is somewhat tangential), it remains well-structured and efficient without redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only tool with two optional parameters and a rich return object, the description is complete. It explains when to use it, when not to, what the return object contains and how to interpret it, and covers the edge case of missing color_scheme. The presence of an output schema reduces the need to detail return formats, and the description provides the semantic guidance needed for correct invocation. Nothing critical is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both parameters are fully documented in the schema. The description adds one meaningful clarification beyond the schema: it explicitly states that omitting workspace_id returns the same answer because tokens describe the package, not workspace data. This is valuable extra semantic info, so a score of 4 is justified over the baseline 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: 'Get Well's colours, shape and type vocabulary' with the explicit goal of making composed views look like Well. It distinguishes itself from all siblings by being the only tool about design tokens, and it specifies the exact resource (colours, shape, type) with a concrete verb 'Get'.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage guidance is explicit and comprehensive. It states the condition 'Call this ONLY when you are about to render something yourself' and provides clear exclusions: 'You do not need it to answer in prose or in a markdown table' and 'Do NOT use it to restyle a card a Well tool already drew.' It also references the alternative (host-rendered cards) and explains why a second styled copy is a duplicate. This is model guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_get_entityGet entity (with sub-resources)ARead-onlyInspect
Read ONE entity with its sub-resources nested in a single call.
Convenience over well_get_schema + well_query_records: resolves the field paths for you and returns the single record with its related data expanded.
depth (relation-nesting BOUNDARY, 1-3, default 1): 1 = the entity + its direct sub-resources (emails, phones, locations, …) 2 = + the sub-resources' related scalars 3 = the full level-3 graph (LARGER payload — use when you need the whole picture) Stops at depth 3. Aggregates are excluded. Each child collection is capped at 50 rows; for a full list or to page a large child collection, use well_query_records on that child root instead.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | The entity's public UUID (the value of its *_id field, e.g. company_id) | |
| root | Yes | Entity type, e.g. companies | people | invoices | transactions | |
| depth | No | Relation-nesting boundary 1-3 (default 1). | |
| workspace_id | No | Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| depth | No | |
| error | No | |
| found | Yes | |
| entity | Yes | |
| success | Yes | |
| columnMeta | No | Per-column field meaning ({context, enrichment}) for documented columns — read this to interpret the entity's values. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| fields_selected | No | |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark this as read-only and non-destructive, and the description adds meaningful behavioral detail beyond that: it caps child collections at 50 rows, excludes aggregates, stops at depth 3, and warns that depth 3 produces a LARGER payload. It also discloses that it resolves field paths automatically rather than requiring the caller to specify them, which is important hidden behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is organized into a clear purpose statement, a convenience rationale, and a bullet-like depth breakdown. Every section earns its place, and the most important scoping and limit information is front-loaded while the alternatives are clearly appended.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the tool's scope, limits, exclusions, depth semantics, and the appropriate fallback for pagination, which is everything an agent needs to decide whether and how to call it. The output schema exists, so return-value detail is not required from the description, and annotations already cover the safety profile.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 enriches the depth parameter substantially by defining exactly what level 1, 2, and 3 return and the 50-row cap. It does not add much to id/root/workspace_id/conversation_id beyond the schema, but the depth semantics alone justify a point above baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Read ONE entity with its sub-resources nested in a single call', naming the exact verb, resource scope, and call shape. It also distinguishes itself from well_get_schema + well_query_records, so an agent can immediately tell this is the convenience read tool for a single entity graph.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly frames itself as a convenience over well_get_schema + well_query_records and explains that it resolves field paths for you. It also gives depth-level guidance and directs users to well_query_records when they need a full list or to page a large child collection, making the alternative selection clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_get_graphGet workspace graphARead-onlyInspect
Draw the workspace's context graph — its companies, people, accounts, transactions and connectors, and the connections between them — as an interactive canvas the user can orbit, zoom and hover.
Use it when someone asks to see how their business data connects, wants a picture of the workspace, or asks what a company or person is linked to. Narrow the drawing with perspective ("contacts", "money", "accounting"), time_window, min_degree (thin a dense workspace to its hubs) and company_cap.
The card renders the graph itself. This tool's text result reports only the counts, so say what the shape shows rather than listing nodes. When at_company_cap is true the drawing holds as many companies as the cap allows and the workspace may hold more — say the view is capped rather than describing it as the whole graph.
This tool reads only — it changes nothing.
| Name | Required | Description | Default |
|---|---|---|---|
| min_degree | No | Drop nodes with fewer than this many connections. Raise it to thin a dense workspace down to its hubs; 0 keeps every node. | |
| company_cap | No | Cap the number of company nodes drawn. Omit for the server's unbounded default. | |
| perspective | No | Which slice of the graph to draw. "all" is everything; "contacts" is people and companies; "money" is transactions and accounts; "accounting" is the ledger side. Defaults to the server's own default when omitted. | |
| time_window | No | How far back to reach for the underlying records. Defaults to the server's own default when omitted. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| edge_count | No | Connections between the shipped nodes. |
| node_count | No | Nodes the server actually shipped, after every filter. |
| company_cap | No | The company cap the server applied, when one was. |
| failed_count | No | Open work that cannot progress on its own: stuck tasks and errored connectors. |
| at_company_cap | No | True when the drawing holds as many company nodes as the cap allowed, so the workspace may hold companies it does not show. It is not a count of what was left out — the projection carries no dropped-row signal. |
| own_company_id | No | The workspace's own company node, when one is resolved. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| processing_count | No | Open work that may still change the scene: queued or running tasks, plus connectors mid-sync. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and destructiveHint=false, so the description doesn't need to repeat that. It adds valuable context: the tool is read-only (stated), the text result reports only counts (not node listings), and the at_company_cap behavior is disclosed. This goes beyond annotations to explain how to interpret results, which is critical for correct agent behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured: first sentence defines the tool's core purpose, followed by usage conditions, then guidance on interpreting results, and finally a clear read-only statement. It's front-loaded with the most important info and every sentence earns its place. No fluff or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool has an output schema, the description doesn't need to detail return values. It covers the key aspects: what the tool does, when to use it, how to adjust output via parameters, how to interpret results (counts vs graph), and the at_company_cap edge case. The parameters are well-documented in the schema, and the description fills the behavioral gaps. For a visualization tool, this is complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so parameters are fully documented in the schema. The description adds context on how to use them (e.g., 'thin a dense workspace to its hubs' for min_degree, 'narrow the drawing' for perspective), but this is complementary rather than essential. For a tool with 6 well-described params, the baseline 3 is appropriate 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.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool draws the workspace's context graph (companies, people, accounts, etc.) and visualizes connections. It distinguishes itself from siblings like well_get_entity or well_search_context by focusing on the graph visualization. The verb 'draw' and resource 'context graph' 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly states when to use: when someone asks to see how business data connects or wants a picture of the workspace. It also lists narrowing parameters (perspective, time_window, min_degree, company_cap) and provides behavioral guidance on what to say when at_company_cap is true. It implies when not to use (e.g., for specific entity details) by contrasting with the graph's scope.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_get_investment_holdingsGet investment holdingsARead-onlyInspect
Get the live holdings/positions (what's currently held and its value) for a connected Plaid investment account — brokerage, IRA, 401k, etc.
WORKFLOW:
well_list_connectors() → pick the ENABLED Plaid connector (connection_status: "enabled") and read its workspace_connector_id directly off the row.
well_get_investment_holdings({ workspace_connector_id }) → the current holdings, fetched fresh from Plaid on every call (never stored/stale data).
Only works on Plaid connectors that support the investments product — not the MCP-transport connector-tool-passthrough tools (well_list_connector_tools / well_invoke_connector_tool), and not for investment transactions (buy/sell/dividend/fee), which are queryable as ordinary rows via well_query_records on the transactions root instead.
| Name | Required | Description | Default |
|---|---|---|---|
| workspace_id | No | Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| workspace_connector_id | Yes | The connected Plaid provider's workspace_connector_id (from well_list_connectors). |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| holdings | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark the tool read-only and non-destructive, and the description adds the valuable behavioral guarantee that holdings are 'fetched fresh from Plaid on every call (never stored/stale data).' It also discloses the product constraint (only Plaid connectors supporting the investments product) and what the tool is not for, with no contradiction to annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose, then uses a labeled WORKFLOW and explicit exclusions. Every sentence earns its place; the length is justified by the routing decisions an agent must make and there is no filler or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the annotations, a full output schema, and detailed parameter descriptions, the description supplies the essential missing workflow glue: how to obtain the workspace_connector_id, which sibling tools are not the right ones, and where to go for investment transactions. Nothing critical is missing for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already documents all three parameters in detail (100% coverage), so the baseline is 3. The description adds extra operational meaning beyond the schema by telling the agent to pick the connector with connection_status 'enabled' and to read workspace_connector_id 'directly off the row,' which is useful parameter-filling guidance not present in the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a concrete verb and resource: 'Get the live holdings/positions (what's currently held and its value) for a connected Plaid investment account.' It also differentiates itself from connector passthrough tools and investment-transaction queries, so an agent can distinguish it from siblings like well_list_connector_tools and well_query_records.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The WORKFLOW section gives an explicit two-step recipe: call well_list_connectors, select only the ENABLED Plaid connector, read workspace_connector_id, then call this tool. It further states exclusions ('not the MCP-transport connector-tool-passthrough tools', 'not for investment transactions') and names well_query_records for transactions, making when-to-use and when-not-to-use unambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_get_own_companyGet own companyARead-onlyInspect
Get which company the workspace itself is: the confirmed own-company anchor (anchor) and any detected companies not yet confirmed as it (candidates).
Use this whenever a question turns on "mine" versus "theirs" — my payables, my receivables, invoices I owe, what we billed — and then filter by the company_id this returns. Never decide which records are the workspace's own by comparing a company NAME: the same legal entity appears under several labels (a registered name, a trade name, a bank-issued label), so a name filter silently drops rows.
Returns anchor (company_id, registered_name, trade_name) or null when the workspace has not resolved one yet, and candidates (each with company_id, names, role, confidence_score, state).
anchor: null means the workspace has no confirmed own company. Say so plainly and do not promote a candidate to the anchor yourself — a candidate is a detection, not a decision, and confirming one is a user action.
⚠️ TO ASK THE USER WHICH COMPANY on a card so they can pick or search for it, call well_show_company_candidates INSTEAD: it draws a tile per candidate with a registry search and waits for the click. This read draws nothing.
Registry tax ids and registered addresses are deliberately not returned.
Call this directly — no other tool call is needed first. Both the anchor and the candidates are read from the same workspace this call is scoped to.
| Name | Required | Description | Default |
|---|---|---|---|
| workspace_id | No | Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| anchor | Yes | |
| success | Yes | |
| candidates | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and destructiveHint=false, and the description adds meaningful behavior beyond that: `anchor: null` means no confirmed own company, candidates must not be promoted to anchor, registry tax ids and addresses are intentionally omitted, and this read draws no UI. 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but front-loaded with the core behavior and each block earns its place: the mine-vs-theirs usage rule, the explicit alternative routing, the null-anchor handling, and the withheld-fields disclosure. The warning callout for `well_show_company_candidates` is particularly well placed.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read tool with an output schema and safety annotations, the description covers everything an agent needs: what the two outputs mean, how to handle null anchor, what is not returned, and how to route interactive candidate selection to a sibling. Nothing material is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 fully. The description adds scoping context ('read from the same workspace this call is scoped to') but no parameter syntax or format details beyond the schema. Baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'Get which company the workspace itself is', and concretely defines the two outputs (`anchor` and `candidates`). It explicitly distinguishes itself from the sibling `well_show_company_candidates`, 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives clear when-to-use guidance ('whenever a question turns on "mine" versus "theirs"'), an explicit anti-pattern (never filter by company NAME), and names the alternative to use instead for interactive selection (`well_show_company_candidates`). It also states no prerequisite call is needed.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_get_schemaGet schemaARead-onlyInspect
Discover available data types and fields.
USAGE:
well_get_schema() → List ALL available roots, including the accounting graph (ledger_accounts, journals, journal_entries) plus account_balances, tax_rates, exchange_rates — query these for real financial statements (compte de résultat / balance sheet) instead of reconstructing them from raw invoices
well_get_schema({ root: "invoices" }) → List all available fields for invoices
WORKFLOW:
Call well_get_schema(root) to see available fields
Pick the fields you need for your task (typically 5-15)
Call well_query_records with those specific fields
Returns fields with path, type, and (when documented) semantic context:
{ path: "invoices.grand_total", type: "numeric", context: "Total invoice amount incl. tax in the document currency...", enrichment: "AI extraction" } → use _eq, _gt, _lt, etc.
{ path: "invoices.local_currency", type: "enum" } → use ONLY _eq, _neq, _in, _nin, _is_null
{ path: "invoices.issuer.name", type: "text" } → use _eq, _like, _ilike, etc.
"context" (when present) explains what the field MEANS in the domain and how it's used — read it to pick the right field and write correct filters.
"enrichment" (when present) is the value's provenance (e.g. "Bank sync", "AI extraction", "System generated", "Derived", "Manual"). Use the type to choose the right whereClause operators in well_query_records. To use in well_query_records, convert path to array: "invoices.issuer.name" → ["invoices", "issuer", "name"]
| Name | Required | Description | Default |
|---|---|---|---|
| root | No | Entity root to inspect. Omit to list every available root (call well_get_schema() with no argument first). Includes the accounting graph (ledger_accounts, journals, journal_entries) alongside companies, invoices, transactions, accounts, and more. | |
| depth | No | Relationship depth: 0=scalars only, 1=direct relations (default), 2=nested, 3=level-3 graph | |
| workspace_id | No | Target workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| root | No | |
| error | No | |
| roots | No | |
| total | No | |
| fields | No | |
| success | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already provide readOnlyHint=true and destructiveHint=false. The description adds real behavioral context beyond that: the shape of returned entries (path, type, context, enrichment), how field type constrains whereClause operators, and that workspace_id is optional because this tool describes the token itself rather than a workspace's data. This materially enriches what the annotations alone convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is longer than average, but it is well organized into USAGE, WORKFLOW, and return-format sections with front-loaded purpose. A small amount of repetition exists around operator guidance and path conversion, but each major block earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For an introspection tool with an output schema, 100% parameter coverage, and safe read-only annotations, the description is complete: it covers invocation patterns, a canonical example, the expected workflow before querying, and the meaning of returned fields. 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.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Since schema coverage is 100%, the baseline is 3, and the schema already documents root, depth, workspace_id, and conversation_id. The description adds value beyond the schema by explaining operator rules per type (enum uses ONLY _eq, _neq, _in, _nin, _is_null) and by giving the path-to-array conversion rule for well_query_records. That lifts it above baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first line, 'Discover available data types and fields,' states a specific verb and resource. The usage examples clearly distinguish the no-argument call (list all roots) from the root-specific call (list fields), and the workflow explicitly routes the agent onward to well_query_records, making the introspection role unmistakable relative to sibling tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit call order: call well_get_schema() first to see roots, then with a root to see fields, and then call well_query_records. It also calls out the accounting-graph roots for financial statements. It does not explicitly discuss when not to use this tool versus well_get_entity or well_get_graph, so it stops short of the highest bar.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_get_session_digestGet session digestARead-onlyIdempotentInspect
Get everything a returning person's first answer needs, in one call: what happened in the workspace since they last looked, where the workspace stands now, and the Well skills that can take it forward.
When the person asks what happened since last time, asks to be caught up, or opens a session, do not call this first: load the signing-back skill with well_get_skill and follow it. That procedure greets, reads this digest, and proposes the next steps; calling this tool alone skips the greeting and the proposals. Call this tool directly only when a loaded Well skill says to, or when the person asks for the raw counts and nothing else.
Returns records (one entry per record type with its created / updated / deleted counts and the connectors those creations came from), errors (the pipeline failures worth acting on), skills_run (the Well skills this person already ran recently, so you do not propose one they just finished), and boundary + since_at saying where the window starts. is_first_session true means there is no earlier moment to report from: greet the person and skip the recap. truncated true means the window stopped at 5000 events and the counts cover part of the tail only.
Also returns situation, the state behind the recap, so no follow-up read is needed: connectors (the tools this workspace connected, each with its connection_status and last_successful_sync_at, beside connected_count; the size of Well's catalog is not carried, because it is never a figure to tell the person), open_period (the month Well opens the close on, with its label, is_complete and selectable), and missing_invoices (that month's row_count of counterparties with settled spend and no invoice, plus its hints). Each part is null when its read refused or had nothing to read. A null says the part is UNKNOWN: never report it as an empty connector list, a workspace with no open month, or a month owing nothing.
And skills: the whole Well skill roster, the same entries well_search_skill returns, so a step a click names is loaded with well_get_skill rather than searched for again. roster_readable false says the roster could not be read at all, so skills is empty because nothing loaded: propose no next step in that turn, because every slug would be invented.
And suggested_steps: the five next steps Well ranked for this workspace, each a skill from the roster and the reason it ranks there. The rubric runs on the server: open gaps first (no bank, a month owing invoices, no accounting tool, a stale sync), then the open month's close, then the analysis skills, and never a skill served in the last day. Hand these five to well_propose_next_steps in this order and rank nothing yourself. The sentence each one is offered as is yours to write, in the language the person is using, from that skill's own quoted utterances in skills and the figures above. When the list is empty, nothing was offerable: call no render tool and write no five of your own, say so in one line.
Pass mark: true to advance this person's read cursor to the end of the window, so the next digest starts where this one ended. That cursor is shared with the app, so marking here also clears what the app shows as unread. Pass mark: false (or omit it) to inspect the digest without moving anything.
The figures are computed by Well. State them as returned: do not re-count, round, or total them yourself.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| mark | No | Advance this person's read cursor to the end of the window, so the next digest starts where this one ended. Pass true when you are about to report the digest to the person, false when you are only inspecting it. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| errors | Yes | |
| reason | Yes | |
| skills | Yes | The whole Well skill roster, the same entries well_search_skill returns. The step a click names is loaded from it with well_get_skill. |
| records | Yes | |
| success | Yes | |
| boundary | Yes | Where the window starts: the person's read cursor, their previous sign-in, or neither. Null on a refusal. |
| since_at | Yes | The instant the window starts from, set only when a previous sign-in bounded it. |
| situation | Yes | Where the workspace stands now: its connected tools, its open month, and what that month still owes. |
| time_zone | Yes | The IANA time zone the label was written in, from the workspace's settings. |
| truncated | Yes | True when events sit past the read cap of 5000, so the counts cover part of the tail only. |
| skills_run | Yes | |
| since_at_label | Yes | The same instant written in the workspace's own time zone, such as "Tuesday 8 September at 09:50", ready to quote in the greeting. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| roster_readable | Yes | False when the roster could not be read at all, so `skills` is empty because nothing was readable rather than because the build ships no skill. Propose no next step on false: every slug would be invented and refused. |
| suggested_steps | Yes | The five next steps Well ranked for this workspace, in card order, from its gaps, its open month and the skills already run. Hand these five to well_propose_next_steps in this order, never reordered or replaced. You write the sentence each one is offered as, in the language the person is using, from that skill's own quoted trigger utterances in `skills` and the figures this digest returned. Empty means nothing was offerable: draw no card and write no five of your own. |
| is_first_session | Yes | |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses a state-changing behavior: passing mark: true advances the read cursor and clears the app's unread state. This contradicts the annotations' readOnlyHint: true, which implies no mutation. Per the rubric, a description that contradicts annotations earns a 1 even though the disclosure itself is detailed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but front-loaded with purpose and usage, then organized into clear sections for return fields, null semantics, skills, suggested steps, mark behavior, and workspace selection. It could be tightened by trimming some output-field explanation that may duplicate the output schema, but each section earns its place given the tool's complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 3-parameter tool with 100% schema coverage and an output schema, the description is exhaustive: it covers call routing, mark side effects, workspace disambiguation, null/unknown semantics, truncation, roster failures, suggested-step ordering, and the rule to state figures as returned. Nothing operational needed to call or interpret the tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 beyond the schema: mark advances the cursor and is shared with the app, false means inspect-only, and workspace_id should be passed when the token authorizes multiple workspaces because the tool will not guess. This exceeds the schema's descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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 returns everything a returning person's first answer needs, including workspace activity, current state, and relevant Well skills. It also distinguishes itself from related tools by naming the signing-back skill workflow and well_search_skill, so an agent can tell this tool apart from its siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-to-call and when-not-to-call guidance: for catch-up requests it says load the signing-back skill first instead, and direct calls are reserved for when a loaded skill says so or when the person only wants raw counts. It also explains mark true/false semantics and workspace_id requirements for multi-workspace tokens.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_get_skillGet skillARead-onlyInspect
Get the Well procedure for a job, written by the Well team, and follow it exactly.
Returns ONE markdown document: the instructions for the thing you are about to do. Its content is the instruction, not background reading — do what it says, in the order it says, and do not substitute your own plan for it.
A document may tell you to run other Well skills. Load each one with this tool, by the id the document names, at the moment the document says to.
Use well_search_skill first when you do not already hold the id. The catalog is fixed for the life of the server, so re-fetching a document you already hold buys nothing.
| Name | Required | Description | Default |
|---|---|---|---|
| skill | Yes | The id of the skill to load, as well_search_skill lists it. | |
| workspace_id | No | Target workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| bytes | Yes | |
| error | No | |
| skill | Yes | |
| reason | Yes | |
| success | Yes | |
| document | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark this read-only and non-destructive, and the description adds meaningful behavioral context beyond that: the tool returns exactly one markdown document, the content is mandatory instruction, and the returned document may direct the agent to invoke other Well skills. This is a clear behavioral contract, not a paraphrase of 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose, and every subsequent sentence earns its place: return format, instructional nature, sub-skill loading, search-first guidance, and no re-fetching. It is substantive without being padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the output schema and annotations, the description covers everything an agent needs to call this tool correctly: what it returns, how to use it, when to search first, and why repeated fetches are wasteful. The conversation_id and workspace_id behaviors are fully documented in the schema, so no critical context is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 and the schema already documents skill, workspace_id, and conversation_id. The description adds some practical context around the id and the fixed catalog, but it does not meaningfully enrich the parameter semantics beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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: get the Well procedure for a job. It clearly distinguishes this from well_search_skill by framing search as the precursor for obtaining an id, and it states that the tool returns instructional markdown, not background material.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit usage guidance: use well_search_skill first when you lack the id, load referenced skills at the moment the document says, and do not re-fetch a document you already hold because the catalog is fixed. It also tells the agent to follow the retrieved procedure exactly, which removes ambiguity about how to act on the result.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_get_statement_import_resultGet statement import resultARead-onlyInspect
Read the outcome of a bank-statement upload started with well_create_statement_upload, by the document_id that tool returned.
well_create_statement_upload already renders a card from its own result — this tool does not create or redraw it. Call it once, shortly after the client has uploaded the file bytes, to learn what happened. The card polls this import result itself until it settles, so call this tool again only if the user asks.
status "not_found_yet": the upload has not landed yet — a NORMAL result right after minting the slot, not an error. Poll again once the file has been uploaded.
status "processing": the file is uploaded and the statement is still being extracted / promoted.
status "imported" | "needs_account" | "duplicate" | "skipped" | "failed": the terminal outcome. On "imported", matched_count / review_count / minted_count / already_present_count report the promotion's own snapshot counts, taken once at import time and covering every promotable line of the file disjointly; null on any of them means the row predates count tracking — treat as unknown, never as 0.
recordslists the minted transactions only — matched or ambiguous lines link an existing transaction and are excluded;graphis the frozen record graph for the same snapshot;records_urlopens the workspace's transactions table.
This tool reads only — it changes nothing.
| Name | Required | Description | Default |
|---|---|---|---|
| document_id | Yes | The document_id well_create_statement_upload returned — pre-allocated at mint, before the upload lands. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| hint | No | |
| error | No | |
| graph | No | The frozen record graph for this import, taken from the same snapshot as `records`. Never present on "processing" or "not_found_yet". Like `records`, its counterparty fields are null (frozen before resolution runs) — see `records_url` for the live values. |
| phase | No | |
| reason | No | |
| status | No | |
| records | No | Transactions this import minted as new rows — matched or ambiguous lines link an existing transaction and are excluded. Present only on a terminal "imported" result that carries a snapshot. The snapshot freezes at mint time, so `counterparty` is always null here — resolution runs asynchronously after import; open `records_url` for the live resolved value. |
| success | Yes | |
| document_id | No | |
| imported_at | No | |
| records_url | No | Login-gated deep link to the workspace's transactions table — opened on the first minted record when `records` is non-empty, otherwise the plain table. |
| minted_count | No | Lines minted as new transactions. Absent means the row predates count tracking — treat as unknown, never as 0. |
| review_count | No | Lines skipped as an ambiguous cross-connector match, pending review. Absent means the row predates count tracking — treat as unknown, never as 0. |
| matched_count | No | Lines linked to an existing cross-connector transaction. Absent means the row predates count tracking — treat as unknown, never as 0. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| already_present_count | No | Lines an earlier import already carried — a partial-overlap re-export mints only the new lines, so the four counts cover the file's promotable lines. Absent means the row predates count tracking — treat as unknown, never as 0. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
| counterparties_pending | No | Whether any minted transaction still awaits its counterparty. True right after import while the from/to parties resolve in the background; absent when the result carries no minted transactions. |
| statement_extraction_id | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already say readOnlyHint=true and destructiveHint=false, and the description reinforces this with 'This tool reads only — it changes nothing.' It goes further by disclosing important runtime behavior: 'not_found_yet' is not an error, null counts mean unknown rather than zero, and 'records' contains only minted transactions. These details materially shape how an agent interprets results.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but every sentence earns its place: core purpose, call cadence, status semantics, null handling, records scope, and read-only guarantee. The bullet list for statuses makes the dense information scannable, and the most important usage instruction is front-loaded before the status details.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a polling/query tool with three parameters, strong annotations, and an output schema, the description covers everything an agent needs: when to call, what each status means, how to interpret nulls, what records contain, and that the operation is side-effect free. Even without the output schema, the description would be almost sufficient.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Input schema coverage is 100%, so the schema already fully documents document_id, workspace_id, and conversation_id. The description adds contextual value by tying document_id to the value returned by well_create_statement_upload, but it does not add meaning beyond what the parameter descriptions already provide. Baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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 outcome of a bank-statement upload started with well_create_statement_upload, by the document_id that tool returned.' It clearly separates this read tool from its creation counterpart by stating it does not create or redraw the card. No agent could mistake it for the upload or create 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit call timing: 'Call it once, shortly after the client has uploaded the file bytes' and explains when not to call again: 'The card polls this import result itself until it settles, so call this tool again only if the user asks.' It also treats 'not_found_yet' as a normal early state and instructs to poll again after upload. This is strong, actionable usage guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_get_worklist_statusGet worklist statusARead-onlyInspect
Ask whether a repair gate is still OPEN, without drawing its card.
Call this BEFORE the worklist read whenever you are checking rather than repairing — the first pass of a gate, and every re-check after the reader has cleared one. open: false means the gate is settled: carry on, and call nothing else.
Only call the worklist read when this says open: true. Those reads draw a card on every call, empty included, so reaching for one to find out whether there is anything to do puts a picker with no rows and a dead button in front of the reader. The tool that draws each card comes back as card_tool.
WORKLISTS, and the scope each one needs:
accounts_needing_company— the accounts with no company attached, or whose ownership is still unknown. No scope.uncategorized_window— the transactions in a window carrying no category. Needsfrom(inclusive) andto(EXCLUSIVE), bothYYYY-MM-DD.unposted_transactions— a period's categorized rows still missing the ledger account they would post to. Needsfiscal_yearandfiscal_period.invoice_sources_for_pick— how many of the vendors the user picked on the missing-invoices card carry a connector that can bring an invoice in. No scope: the pick is on this session's own lane. Ask it BEFORE anywell_list_connectors({ from_selection: true })call, and make that call only when this answers above zero — a pick with no invoice source behind it draws a picker with no rows and a dead button.counterparties_to_categorize— the counterparties whose invoices the named months are still missing and that carry no industry category. Needsperiods, the same[{ calendar_year, calendar_month }]list the card takes.
A scope field the named worklist needs is REQUIRED. Omit one and this refuses: a gate reported clear over the wrong window cannot be told from one that is genuinely clear, and the figure behind it would be computed on that.
success: false means the gate is UNKNOWN, not clear. open is ABSENT on that path, so a failed read can never be mistaken for a settled worklist. Retry once; on a second failure say the gate could not be read and stop, rather than computing a figure on evidence you never obtained.
Most worklists report no COUNT. One row answers "is it open", and the count of what is left comes from the worklist read itself — which you are about to call anyway when the gate is open. Two kinds are the exception and carry count. invoice_sources_for_pick reads its whole set by id in one go, never paged, so the number comes free. counterparties_to_categorize reads the whole month population rather than one row, so the number is already in hand, and it is the same number the card reports as uncategorized_count.
COST: counterparties_to_categorize reads each named month's spend — the same reads its card makes, and a scope with work left in it pays for them TWICE, once here and once when the card draws. A clean scope pays once and skips the card entirely, which is what the check buys. Probe the months the user actually named, not a whole year "to be safe".
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
That holds for every gate but two. invoice_sources_for_pick and counterparties_to_categorize follow their cards instead: well_list_connectors and well_list_counterparties both answer from the token's primary workspace when you name none, so those gates answer from the same one. A probe that refused where its card answers would be describing a different workspace from the card it stands in for. The result names the workspace that answered.
| Name | Required | Description | Default |
|---|---|---|---|
| to | No | `uncategorized_window` only: the day AFTER the window's last, exclusive. | |
| from | No | `uncategorized_window` only: the window's first day, inclusive. | |
| periods | No | `counterparties_to_categorize` only: the calendar months to check, the same list the card takes. Every month must have ended. | |
| worklist | Yes | Which repair gate to check. Each one names its own required scope in this tool's description. | |
| fiscal_year | No | `unposted_transactions` only: the period's fiscal year. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| fiscal_period | No | `unposted_transactions` only: the fiscal period, 1-12 for a month; 13 is the adjustment period and resolves to no rows. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| open | No | Whether the worklist still holds a row. Absent when `success` is false — an unknown gate, not a clear one. |
| count | No | How many rows the gate still holds. Carried only by the kinds whose read counts the whole set (`invoice_sources_for_pick`, and `counterparties_to_categorize` where it equals the card's `uncategorized_count`); absent on the kinds answered one row at a time, and absent whenever `success` is false. |
| error | No | |
| success | Yes | |
| worklist | Yes | The gate that was checked, echoed back. |
| card_tool | Yes | The tool that draws this worklist's repair card. Call it only when `open` is true. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark this as read-only and non-destructive, and the description adds substantial behavioral context beyond that: it does not draw a card, open: false means settled, success: false means UNKNOWN with open absent, two worklists return count, and counterparties_to_categorize pays for month spend twice. No contradiction with annotations appears.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but well-organized with bold section headers and front-loaded key guidance. Nearly every sentence contributes, though there is some repetition (e.g., 'a picker with no rows and a dead button' appears twice) and a few rhetorical asides that could be tightened without losing meaning.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite having an output schema, the description covers everything an agent needs to call this correctly: the exact gate semantics, ordering dependencies, scope requirements, failure behavior, cost implications, workspace resolution, and conversation_id propagation. It is complete for a tool of this complexity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 value by mapping each worklist to its required scope fields, stating that omitted required scope causes a refusal, and clarifying workspace_id and conversation_id handling. It reinforces and extends the schema without replacing it.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific, unambiguous statement: 'Ask whether a repair gate is still OPEN, without drawing its card.' It names the exact resource (repair gate/worklist status) and clearly distinguishes itself from the worklist-read siblings by framing it as a predicate check. It also enumerates the worklists it covers and contrasts with the reads that draw cards.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit sequencing guidance: call this BEFORE the worklist read when checking rather than repairing, and only call the worklist read when this returns open: true. It also names alternatives like well_list_connectors and well_list_counterparties, specifies workspace_id behavior for multi-workspace tokens, and gives retry/stop instructions on failure. This is unusually strong usage guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_invite_membersInvite membersAInspect
Invite one or more teammates into a workspace, or into a workspace group. Use it after well_list_member_candidates, on the people the user chose.
Pass invites — 1 to 20 { email, role }, role admin or member — and a target: { kind: "workspace" } for this workspace, or { kind: "group", group_id } for a group you belong to. Only a workspace owner or admin may invite; a caller without that role is refused.
Returns one results entry per invite: status sent (a new invitation), reissued (an already-pending address got a fresh link), or refused — with refusal_reason naming why, ALREADY_WORKSPACE_MEMBER when the address already has access and INSUFFICIENT_PERMISSIONS when the caller may not invite. invitation_email_sent is false when the invite persisted but the email did not leave, so offer a resend. Each successful result carries person_id for the invited address. Never invite an address already active in the workspace.
Pass notify: false (workspace target only) to create or reissue the pending membership WITHOUT emailing — for the assign-then-invite flow where an owner is assigned by a typed email now and the invitation is sent later from the invite card. Use the returned person_id to assign that person as an owner without a second lookup.
| Name | Required | Description | Default |
|---|---|---|---|
| notify | No | Send the invitation email now. Defaults to true. Pass false to create or reissue the pending membership WITHOUT emailing, when a later explicit step sends it — e.g. assigning an owner by a typed email, then sending the invite from the invite card. Applies to a workspace target only; a group invite always notifies. | |
| target | Yes | Where the invites land: this workspace, or a workspace group. | |
| invites | Yes | The people to invite, 1 to 20 per call. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| results | Yes | One result per invite, in the order they were sent. |
| success | Yes | |
| workspace_id | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description goes well beyond the readOnly/destructive hints by detailing side effects: invitations can be sent, reissued, or refused; an invitation may persist without its email; and permission failures surface as INSUFFICIENT_PERMISSIONS. This is exactly the behavioral context needed for a mutating tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but organized into purpose, input, output, and permission/edge-case paragraphs. Code-formatted parameter names and the front-loaded primary use keep it scannable, and every sentence carries actionable information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a write operation with two target kinds and nuanced email behavior, the description covers return statuses, refusal reasons, permission failure, the active-member prohibition, and the delayed-email flow. The output schema and parameter descriptions cover the remainder.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 workflow meaning on top: the 1–20 invites shape, workspace vs group targets, the notify:false use case, and how to reuse the returned person_id to assign an owner.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence states a specific action and resource: inviting teammates into a workspace or workspace group. It is immediately distinguishable from surrounding read/search tools like well_list_member_candidates.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly sequences the tool after well_list_member_candidates and states when not to invite (address already active). It also gives the permission precondition (owner/admin only) and the notify:false assign-then-invite flow.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_invoke_connector_toolInvoke connector toolADestructiveInspect
Run one tool on a connected provider's own MCP server, on behalf of this workspace's connection: an action the user asked to take there (create a record in Attio), or a read of content Well does not sync (a page in a docs tool, a note in a CRM, a file the user pasted a link to).
It is NOT a way to read financial data: Well already syncs invoices, transactions, accounts and the accounting graph from every connected provider — read those with well_query_records instead of calling a provider's own list/read tools.
WORKFLOW:
well_list_connectors() → pick the ENABLED provider (connection_status: "enabled") and read its workspace_connector_id directly off the row. A workspace_connector_id the user pasted is fine to use as-is: it is resolved inside this workspace, and an id that does not belong here fails server-side.
well_list_connector_tools({ workspace_connector_id }) → the live tool names + input schemas that connection actually exposes right now.
well_invoke_connector_tool({ workspace_connector_id, tool: "<one of the names from step 2>", args: { ... } }).
Only works on connectors that expose an MCP server (e.g. Attio, Notion, Linear) and whose connection is enabled. Returns the provider's tool result, or { success: false, error } if the tool failed / is not granted.
| Name | Required | Description | Default |
|---|---|---|---|
| args | No | Arguments object passed straight to the provider tool. Omit if the tool takes none. | |
| tool | Yes | The provider tool name to run (one of the connector's available_tools). | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. | |
| workspace_connector_id | Yes | The connected provider's workspace_connector_id (from well_list_connectors). |
Output Schema
| Name | Required | Description |
|---|---|---|
| tool | No | |
| error | No | |
| result | No | |
| success | Yes | |
| error_code | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark the tool as non-read-only, open-world, and potentially destructive. The description adds useful behavioral context: it can either mutate provider data or read unsynced content, returns the provider result or { success: false, error } on failure/grant denial, and invalid workspace_connector_ids fail server-side. This meaningfully goes beyond 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is longer than average but well-organized: clear purpose, explicit exclusion, numbered workflow, and a concise constraint paragraph. It is front-loaded with the core action and the 'NOT' condition. Minor redundancy (MCP/enabled requirements repeated) prevents a 5, but every sentence adds selection or invocation value.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (6 parameters, nested objects, output schema present), the description covers the essential call path (workspace_connector_id, tool, args) and the prerequisite discovery steps. It explains error behavior and the boundary of supported connectors. The optional parameters (workspace_id, conversation_id, idempotency_key) are fully documented in the schema, so the agent has enough to invoke correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and each parameter already has a thorough description. The tool description adds valuable workflow semantics: the 'tool' parameter must be one of the names from well_list_connector_tools and 'args' must conform to that tool's live input schema, which is not fully stated in the schema itself. This elevates the score above baseline 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Description states a specific verb and resource ('Run one tool on a connected provider's own MCP server') and gives concrete examples (create a record in Attio, read unsynced docs/CRM content). It explicitly contrasts with well_query_records for financial data, making sibling differentiation immediate and clear.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit when-to-use (actions user asked to take, reads of content Well does not sync) and when-not-to-use (financial data, routed to well_query_records instead). Includes a numbered workflow naming prerequisite tools (well_list_connectors, well_list_connector_tools) and states the connection must be enabled and expose an MCP server.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_list_account_balancesList account balancesARead-onlyInspect
List every account on the workspace with its stored balance. Rows only — this tool holds no definition of cash, and returns no figure the app renders.
Use it when you are computing a cash figure whose RULES you are stating yourself: which accounts belong to the business, which account types count as cash, which stored field is "the balance", what each currency converts at. The server derives no cash position of its own from this call, so a cash figure starts here: state the rules, keep exactly the rows they admit, then put the result on a card with well_render_cash_position — or, when the answer is cash month by month rather than one total, with well_render_cash_forecast.
It applies no scope. Every active account comes back, including ones you will almost certainly exclude. ownership is workspace, counterparty or unknown, and it decides membership together with company_id:
workspace— the business's own, EXCEPT whenown_company_idis set AND the row names a different company. A row with nocompany_idis trusted, because a connector tags a row before any holder is known; so is a row naming a company whileown_company_idis stillnull, because nothing has disproved the pairing yet. Only a tag contradicting a resolved anchor is stale, and counting that one widens the owned scope and overstates the figure.counterparty— not the business's, unconditionally.unknown— unsettled, and settled ONLY by the anchor: own whencompany_idequalsown_company_id, a counterparty's when it names a different one.
own_company_id is null when the workspace has not set one. Nothing is settled against it then — no unknown row, and no workspace row's company pairing either — so say so rather than counting or dropping on a guess. This is the same three-way rule the app's own canvas account scope applies, and a figure that departs from it disagrees with the number the product shows.
It applies no type filter. account_type is one of deposit, credit, loan, investment, payroll, other. A credit or loan account is a liability, so its balance normally nets out of cash rather than adding to it — but that is your decision to state, not a fact about the row, and the sign stored is the sign the provider sent.
It chooses no amount. closing_booked is SETTLED cash; closing_value includes pending and uncleared movements. The two differ by every initiated-but-unsettled payment, so which one you total is the single most consequential choice a cash figure makes: state it. opening_booked is the fallback for a freshly-opened balance with no settled activity yet. Any of them is null when the stored value was absent or not a finite number, which is not a zero balance.
It converts nothing. Each reading carries its own currency, which can differ from the account's own account_currency. Convert per row at a rate you can state, then total — a sum across currencies is denominated in nothing and no field here would say it happened.
It lists each copy of an account. One physical account can arrive once per connector that syncs it. duplicate_of_account_id names the account a row is a second copy of, and is null on every other row. Leave a marked row out of every total and every count: the balance that counts is the named account's, which is the one the app's own figure reads, and the transactions of both copies are counted once. Total both copies and the figure holds that money twice. When the named account has no readable balance, report it as having none rather than taking the copy's reading in its place, or the figure departs from the app's. Marking follows the ownership rule above: only the business's own accounts are marked, and an unknown row only once own_company_id settles it.
balance is null when no row was selected for that account. Two different situations produce it and they must not be reported the same way: verification_rejected: true means the newest balance failed verification and the bounded walk back found no verified one, so the data is repudiated; false means the account simply has no history yet.
months_back adds month_ends to every row: one reading per complete month end, oldest first, keyed YYYY-MM, ending on the last COMPLETE month. This series is where a cash forecast starts — "what will our cash look like", "project our cash forward", "when do we hit zero" — and it is the settled half of well_render_cash_forecast; the projected half is that series' last settled month minus the burn you measure with well_sum_transactions. A null reading is a month no stored row covered — not a zero balance, so never plot it as one and never interpolate between two real points. Omit months_back for the current reading alone; the series is a second query and is not free.
partial: true means the read was cut short BEFORE RETURNING ANYTHING, so it always arrives with an empty rows — it is a fact about the call, never a coverage figure over rows you received. Nothing is known about what is there, so derive no figure from it: say the read was cut short and offer to try again. unreadable_rows is the separate case and the only one that continues: the read finished, and that many rows carried a stored balance that could not be parsed. They hold a null balance, sit in no figure, and make any total a floor, by up to their count: the count covers every row, whatever its owner or type.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| months_back | No | How many complete month ends to carry per account, oldest first. Omit for the current reading alone. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes | |
| error | No | |
| partial | Yes | |
| success | Yes | |
| base_currency | Yes | |
| own_company_id | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| unreadable_rows | Yes | |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint/destructiveHint annotations, the description discloses numerous behavioral traits: it applies no scope, no type filter, no amount selection, and no currency conversion. It explains partial reads, unreadable_rows, null balance meanings, verification_rejected, duplicate handling, and the months_back series, all of which materially affect the output. No contradiction exists 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose and then organized into clear thematic paragraphs (scope, type, amount, currency, duplicates, nulls, partial reads, months_back, workspace). It is long, but each paragraph earns its place by addressing a distinct misinterpretation an agent could make; only slight trimming could improve it.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with this much behavioral nuance, the description is remarkably complete: it covers ownership rules, account type implications, amount selection, currency conversion, duplicates, null semantics, partial reads, unreadable_rows, and workspace selection. Since an output schema exists, the description doesn't need to explain return fields, and every call-relevant aspect is addressed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Input schema covers 100% of parameters, so baseline is 3. The description adds semantic depth: it explains that months_back is a second query that returns one reading per complete month end, that workspace_id must be passed when multiple workspaces are authorized, and that conversation_id must be passed back on every call. This goes beyond the schema descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence states a specific verb and resource: 'List every account on the workspace with its stored balance.' It immediately distinguishes itself from rendering tools by saying 'Rows only — this tool holds no definition of cash, and returns no figure the app renders,' which separates it from siblings like well_render_cash_position and well_render_cash_forecast.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly instructs when to use this tool: 'Use it when you are computing a cash figure whose RULES you are stating yourself...' and routes to well_render_cash_position or well_render_cash_forecast for presentation. It also clarifies the relationship with well_sum_transactions and explains workspace authorization conditions, so an agent knows exactly how to choose it over alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_list_accounts_needing_companyList accounts needing a companyARead-onlyInspect
List the workspace's accounts that cannot yet be placed on either side of a transfer, so a figure that depends on account ownership can say exactly what is missing before it is computed.
Two states, ONE worklist, because they answer one question — whose account is this:
No company attached. Nothing can place the account on either side of a transfer.
ownership: "unknown". The account has a company, and whether the workspace owns it is unanswered.
The second is not the lesser case. An account left unknown sits outside the internal-transfer rule exactly as an unattached one does.
This is a gate on a FIGURE, not a tidiness list. well_sum_transactions with exclude_internal_transfers keeps the rows with exactly one leg on an account the workspace OWNS, and drops the two-leg ones. So an account's ownership decides whether its movements count as money leaving the business. An account wrongly marked as the workspace's own removes real spend from the figure, quietly, with no error anywhere.
Do not propose an owner of your own. You cannot read one off an account's name, its bank, or the company that appears most often beside it — a name-shaped match proposes the company minted FROM that name, and the bank that issues an account is not its owner. Where the system HAS a grounded proposal it rides on the row as company_suggestion, and the card is where a reader accepts it. unknown is a truthful state and a wrong classification is not.
Each row carries account_id (pass it to well_assign_account), account_name, iban, currency, the company_id and company_name already attached when the gap is the ownership rather than the link, and ownership.
own_company_id names the company that IS the workspace. It is what settles ownership without guessing: an account attached to that company is the business's own, and one attached to any other company belongs to a counterparty. When it is null the workspace has set no anchor, so nothing here settles ownership and the account stays unknown until a reader says otherwise.
The companies a reader can pick ride alongside the rows, capped. When the workspace holds more than the cap, narrow them with company_search rather than assuming the card carries every company.
A row whose ownership is already workspace carries company_suggestion: the company that IS the workspace, which is what such an account belongs to by definition. The field is ABSENT on a counterparty or still-unknown row, and on a workspace with no anchor set — absent means nothing grounded a guess, never that the row was checked and has no owner. It is a proposal for a reader to accept, not a decision: ownership decides whether an account sits in the workspace's own set at all, so never write it without the reader choosing it.
truncated: true means the page filled and more accounts exist, so report the count as a floor rather than as the total.
success: false means the worklist is UNKNOWN, not empty. The read failed, so no count exists. An empty records on a failed read is not "every account is settled" — treating it that way lets a figure be computed on evidence it never obtained.
When the user asks to see, list or fix these rows, call this tool directly: its card is the answer. ⚠️ This tool draws its card on EVERY call, the empty one included. So when nobody asked for the list and you only need to CHECK whether anything is left (the first pass of a gate, or a re-check after a repair), call well_get_worklist_status({ worklist: "accounts_needing_company" }) first. It draws nothing. Call this tool after it only when it answers open: true.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max accounts to return (default 200). | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| company_search | No | Narrows the companies offered on the card by name, server-side. Use it when the workspace holds more companies than one page and the one the user means is not on it. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| records | Yes | |
| success | Yes | |
| returned | No | |
| truncated | No | |
| own_company_id | No | The company that IS the workspace; null when no anchor is set. |
| company_catalog | No | How many companies the card was given against how many the workspace holds. `truncated: true` means the one the user means may not be on the card — narrow with `company_search`. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Well beyond the annotations (readOnlyHint=true), the description discloses that the tool draws its card on EVERY call including the empty one, that success:false means the worklist state is UNKNOWN rather than empty, and that truncated:true makes a count a floor rather than a total. It also documents field semantics such as company_suggestion being ABSENT meaning no grounded guess existed — not that the row was checked and found ownerless — which prevents serious misuse.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with bold-led paragraphs and a strong front-loaded purpose, and nearly every paragraph carries a distinct operational rule. However, it runs to roughly 600 words for a read-only list tool with four documented parameters; sections like 'The second is not the lesser case' and repeated warnings against guessing an owner could be tightened without losing substance.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the domain's subtlety — ownership semantics, the unknown-vs-empty trap, and the relationship to well_sum_transactions — the description covers everything needed: the two states, own_company_id's anchoring role, company_suggestion presence/absence rules, success/truncated meanings, the lighter-weight sibling, and workspace disambiguation. The output schema and full parameter coverage handle returns and syntax, leaving no operational gap for an agent to guess about.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 operational context beyond the schema: companies offered alongside rows are capped and company_search exists to narrow them rather than assuming the card carries every company. workspace_id is also motivated as an answer to 'this read will not guess which one you mean,' which gives meaning beyond the schema's literal text.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence states a specific verb and resource — 'List the workspace's accounts that cannot yet be placed on either side of a transfer' — and explicitly defines the two qualifying states (no company attached; ownership unknown). Frames the result as a gate on a FIGURE rather than a tidiness list, which differentiates it from the many list siblings and clarifies what the list is for.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes between this tool and well_get_worklist_status: call this directly when the user asks to see/list/fix these rows, but call the status tool first when the agent only needs to check whether work remains, and call this only after status answers open: true. Also gives conditional workspace guidance — call directly with a single-workspace token, pass workspace_id when the token authorizes several.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_list_burn_exemptionsList burn exemptionsARead-onlyInspect
List the categories a reader can exempt from burn over one window, each with the spend exempting it would remove. This is what the exemption card offers; it measures nothing the sum did not already measure.
Each entry in groups is one category's OUTFLOW in the window: category_key (the id an exemption is matched on), label (the category as the product writes it), amount (a magnitude, never signed) and count (the rows behind it). Sorted by amount descending, so the biggest decision reads first. A category with no outflow in the window is NOT listed — exempting it would remove nothing, so it is not a choice.
total is the sum of groups[].amount and nothing else. unclassified_amount and unclassified_count are the outflow this list cannot offer as a choice: rows carrying no category, which no exemption ever matches, and rows whose category is outside the shared vocabulary, which this read cannot name for a reader. The two are reported together because both leave the reader's choices unable to touch that money — not because the same thing is true of them downstream. total + unclassified_amount is the window's whole outflow, so a reader can see what the choices do not cover. State the unclassified figure whenever it is not zero rather than presenting total as the whole window.
Pass the convention you elected for the burn figure itself. The list and the figure have to sit on one election, and this read deliberately does not make a second one.
partial: true means the underlying sum measured nothing, so groups is empty and nothing is known about the window's spend. Say so and offer to try again, rather than presenting an empty list as a decision. unreadable_rows counts rows whose amount could not be read at all; they are in no figure here.
from is inclusive and to is EXCLUSIVE, so a window of whole months passes the first instant of the month after the last one you want. window echoes both back exactly as you sent them.
Internal transfers are already out. This read keeps only the rows with exactly one leg on an account the workspace owns — the same rule the burn applies — so a movement between the workspace's own accounts never appears here.
currency is the one currency every row in the window shares. A window holding more than one, or holding a row that carries none, is REFUSED with success: false and an error saying which: adding two currencies gives a number denominated in nothing, and no field on this result could say it happened. currency is the EMPTY STRING only when the window held no row at all, and then groups is empty and both totals are zero.
The direction convention is elected ONCE over the whole window, never per category. A window whose rows are overwhelmingly negative stores an outflow as a negative amount, and these figures are that branch. A window that stores outflows as positive magnitudes keeps the direction in a field this grouping does not read, and a window that pools both kinds of feed has no single outflow at all — both are REFUSED with an error naming the counts behind the decision, rather than reported as spend.
Take the reader's answer from the card, record it with well_switch_workspace as exempt_categories, and read it back with well_wait_for_selection (kind "exemptions"). The record belongs to this conversation. Another conversation does not read it. Then pass the same keys to well_sum_transactions as exempt_categories to compute the burn without them, and name the exemptions beside the figure so it can be read back.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| to | Yes | EXCLUSIVE end of the window, ISO-8601 — the first instant after the last month you want. | |
| from | Yes | Inclusive start of the window, ISO-8601 (e.g. 2026-06-01). | |
| convention | Yes | Which sign means money leaving, as YOU elected it for the figure these exemptions apply to — the same election `well_render_burn` takes. This read does not elect its own: a share of positive rows cannot tell a business with revenue apart from two feeds pooled together, and guessing would either refuse ordinary workspaces or total two conventions as one. Pass "signed" when the window's rows are mostly negative for spend, "magnitude" when the feed stores outflows as positive numbers. A "magnitude" window is refused, because direction then lives in a field this read does not group on. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| total | Yes | The sum of groups[].amount, and nothing else. |
| groups | Yes | |
| window | Yes | |
| partial | Yes | True when the sum behind this card measured nothing. `groups` is then empty, so there is no list to choose from: say so and offer to try again. |
| success | Yes | |
| currency | Yes | The one currency every row shares; empty only when the window held no row. |
| next_step | No | What to do with the card this result renders. Added by the dispatcher when the card asks for a click. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| unreadable_rows | Yes | Rows in the window whose amount could not be read. They are in no figure here, including the unclassified one. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| unclassified_count | Yes | |
| unclassified_amount | Yes | Outflow this list cannot offer as a choice: rows with no category, which no exemption ever matches, and rows whose category is outside the shared vocabulary, which cannot be named here. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the readOnly/destructive annotations by disclosing important behavior: categories with no outflow are omitted, from is inclusive and to exclusive, partial means nothing was measured, currency mixing is refused, internal transfers are excluded, and the direction convention is elected once over the whole window. Also reveals that records are scoped to the conversation. 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long, but the length is mostly earned by the tool's complex refusal and edge-case behavior. It is front-loaded with purpose and output shape and uses paragraph-per-topic structure; it loses a point because some caveats, particularly currency and unclassified amounts, are stated more verbosely than needed.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers return fields, all major refusal branches, multi-workspace behavior, conversation scoping, and the downstream workflow with sibling tools. Despite the long description, nothing an agent needs in order to call this tool correctly appears missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 operational meaning: convention must match the burn election and magnitude/mixed windows are refused, window boundaries are reinforced, and workspace_id is required when multiple workspaces are authorized. It does not add significant new meaning for conversation_id beyond what the schema states, so it is not a 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb and resource: 'List the categories a reader can exempt from burn over one window.' It immediately scopes the tool as the exemption-card view and explicitly says it 'measures nothing the sum did not already measure,' which distinguishes it from related sum/render siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit context: call directly for a single-workspace token, pass workspace_id for multiple workspaces, and reuse the returned keys with well_switch_workspace, well_wait_for_selection, and well_sum_transactions. It gives a clear workflow, but it does not explicitly state when-not to use this tool versus a specific sibling, stopping 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.
well_list_cash_scopeList cash scope by account typeARead-onlyInspect
List the account types a reader can count as cash, each with what it holds. This is what the cash-scope card offers; it measures nothing well_list_account_balances did not already read.
Each entry in groups is one account type: account_type, account_count, and subtotals — one native amount per currency, never converted and never blended. Sorted with the largest holdings first, so the biggest decision reads first. A type with no accounts is NOT listed: counting it would change nothing, so it is not a choice.
Only accounts the workspace owns are folded here. Ownership is settled by a fact about the account, not by a preference, so it is never offered as a choice on this card. excluded_not_owned counts what that removed, and unsettled_ownership counts accounts whose owner is unanswered — those are NOT in any group, and a non-zero count means the reader has a repair to do before any total is trustworthy. Say it rather than presenting the groups as the whole picture.
unreadable_balances counts owned accounts whose stored balance could not be read at all; unreadable_currency counts those carrying an amount with no currency code anywhere. Both are in no subtotal, so state them beside any figure rather than presenting one that silently skipped them — and keep them apart, because they are different repairs: a balance that did not arrive against a row that arrived incomplete.
folded_duplicates counts rows left out because they are a second copy of an account already listed, synced once per connector. They are in no group and no count, since the account they copy is counted once.
partial: true means the underlying read was cut short before it returned anything, so groups is empty and nothing is known about what the workspace holds. Say the read was cut short and offer to try again, rather than presenting an empty list as a decision.
The card records the reader's answer in this session, so wait for it: call well_wait_for_selection({ kind: "cash_scope" }) in the SAME turn, and read selection.counted_account_types. An EMPTY array there is the answer "nothing is cash" — a resolution that ends the run, never a zero total. Do not settle the scope yourself: on a workspace holding more than one type that is the figure decided on the reader's behalf. A card listing no type at all asks nothing and carries no wait. Once the answer is in, YOU apply it when you total the balances, then call well_render_cash_position with the types you counted in scope.account_types.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| groups | Yes | |
| partial | Yes | |
| success | Yes | |
| next_step | No | What to do with the card this result renders. Added by the dispatcher when the card asks for a click. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| folded_duplicates | Yes | |
| excluded_not_owned | Yes | |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| unreadable_balances | Yes | |
| unreadable_currency | Yes | |
| unsettled_ownership | Yes | |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint=true and destructiveHint=false, but the description adds substantial behavioral detail: sorting by largest holdings, omitting zero-count types, ownership filtering, excluded_not_owned, unsettled_ownership, unreadable_balances, unreadable_currency, folded_duplicates, partial:true semantics, and the meaning of an empty selection. This far exceeds the annotation baseline.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long, but it is organized into purposeful paragraphs covering distinct edge cases, and the core purpose is front-loaded. Some imperative phrasing is slightly repetitive ('Say it', 'state them', 'keep them apart'), so it is not maximally concise, but every section contributes operational guidance.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, the description does not need to restate return values. It covers workspace authorization, conversation continuation, the selection-wait contract, empty-selection resolution, follow-up rendering, and all output caveats. 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.
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 reinforces the workspace_id rule and the conversation_id requirement, but it mainly restates what the schema already documents rather than adding genuinely new parameter-level meaning.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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 account types a reader can count as cash, each with what it holds.' It also distinguishes itself from well_list_account_balances by clarifying it 'measures nothing' that tool did not already read, giving the agent a clear basis for selection.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit workflow guidance: call directly when the token authorizes one workspace, pass workspace_id when it authorizes several, call well_wait_for_selection in the SAME turn, and then apply the selection and call well_render_cash_position. It also states when no wait is needed, making the usage conditions unambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_list_connectorsList connectorsARead-onlyInspect
List the connectors a workspace can install AND everything it has already connected, each with a one-click install deep link. The result DRAWS THE CONNECT CARD the user clicks in.
ONE tool answers both halves of the connect question — "what can I connect to Well?" and "what is connected, still syncing, or broken?" — because every existing connection is overlaid onto its catalog row. Do NOT read workspace_connectors records to work out connection coverage; this tool is that answer.
⚠️ FOR A SILENT COVERAGE CHECK, CALL well_get_connector_coverage INSTEAD. Same scope arguments, same rows, no card. A data skill confirming a bank is connected before it measures anything must use that one: this tool renders on every call, so a check run here drops a connect picker into a conversation about something else and then waits for a click nobody meant to make.
⚠️ WAIT ON THE CARD IN THE TURN THAT DREW IT. Write your one line for the user FIRST — the wait holds the turn open for up to a minute, and a user looking at a card with no sentence beside it has been given no reason to click — then call well_wait_for_selection on the kind this result names in next_step.
Each entry has:
service_id: the connector's stable catalog id (e.g. "stripe"), used in the install link.
name, category_id, direction: what the connector is.
data_domains: the financial domains it serves — any of "bank", "accounting", "invoicing" — or null for a non-financial connector. One connector can serve several domains (Qonto serves all three). "bank" here means the connector delivers cash movements, which a payroll or billing platform also does; do NOT read it as "this is a bank". To list banks, pass kind: "bank", which the server scopes on its own bank classification.
invoice_source: this connector can bring supplier invoices into Well, either because it issues or holds them (an accounting or an invoicing tool) or because invoices arrive through it as files (a mailbox, a messaging app, a file drive). Read it to decide which tools to offer for a missing-invoice hunt. It is a property of the connector, not of this workspace's connection.
reason: why this row is on the card. "catalog" is the list that was asked for. "picked_vendor" is a connector behind a counterparty the user picked. Say which is which; never present a catalog row as one the user chose.
status: "available" connectors are connectable now; "coming_soon"/"unavailable"/"maintenance" are not.
is_matched / is_selected: whether this workspace's detected tools matched this connector / already picked it.
match_score: 0..1 confidence of that match; null when unmatched. A high score is a tool Well is confident the workspace already uses.
is_connected: this workspace has a connection you should REPAIR or MANAGE, not install fresh. True for "enabled", "processing", "error" and "need_reconnect"; false for "to_configure" and "disabled", where a fresh install IS the right next step.
connection_status: the existing connection's state, or null when this workspace has no connection row for the connector at all. One of:
"enabled" — connected and syncing.
"processing" — the grant is in and the FIRST sync is still running; data may be partial. Connected: do NOT ask the user to connect it again.
"error" — authenticated but its last real sync failed. Offer install_url as a reconnect.
"need_reconnect" — the grant is dead and only the user can restore it. Offer install_url as a reconnect, NOT a first install.
"to_configure" — a connect attempt that never completed its handshake. Nothing is connected: offer install_url as a first install, and never claim the tool is connected.
"disabled" — the connection was torn down. Offer install_url as a first install. A "degraded" connector never appears: it is resolved server-side against its own sync history into "enabled" or "error", so you never surface a state that clears itself. A connection whose state this build cannot read also reports null, and there is_connected stays true — read the two fields together, and treat "null status, is_connected true" as an existing connection whose health is unknown.
workspace_connector_id: the connection instance's id, or null when there is no connection row. This is the id well_invoke_connector_tool and well_list_connector_tools need — resolve it HERE, never via well_query_records on workspace_connectors.
last_successful_sync_at: ISO timestamp of the last SUCCESSFUL sync, or null when none has landed yet. An "enabled" connector with null here has a valid grant but has never delivered data.
sync_in_progress: a data sync is running right now. Tell the user to wait rather than to act.
is_preselected: Well recommends connecting this one now (a high-confidence match with is_connected false). The interactive picker pre-checks exactly these. A "to_configure" or "disabled" row can still be pre-checked — installing it IS the fix. On kind: "accounting" at most ONE row carries it — the single highest-confidence accounting tool — because connecting the accounting software is a pick-one step; every other scope pre-checks each high-confidence match.
install_url: a one-click link that STARTS or REPAIRS the connection in Well. It works from any state — it signs the user in if needed, creates their workspace if they have none, then runs the connector's own auth flow — and it covers banks too (a bank opens its bank-login flow pre-selected). Null only when the connector is not "available". Hand this to the user to get started in one click.
countries: the ISO 3166-1 alpha-2 countries this connector serves, or null when none is known. It is what the country scope sorts on; use it to explain why a bank fits the company, never to hide a bank the country field is null on.
unsent_document_counts is a TOP-LEVEL field, present only when include_unsent_counts was passed. It names every tool this workspace forwards documents to, with the documents each one has not received yet, biggest backlog first. IT IS THE ONLY PLACE THE BACKLOG IS REPORTED: that array carries the workspace's whole set of outbound connections whatever catalog page came back, the connector rows carry no count at all, and the catalog runs to hundreds of rows, so a tool with a real backlog is missing from any page that did not happen to carry it. An entry reading 0 is a tool that is up to date: say nothing about it. unsent_document_count_is_upper_bound true means a document filter applies to that connection, so the count is a maximum and reads as "up to ". Every count is read off Well's own forward record, so it measures what Well wrote down rather than what the tool confirmed. Name each tool by the entry's name.
install_all_url is a TOP-LEVEL field, not a per-connector one. It is ONE link that installs every installable connector in this result that is not already connected, in the order they are listed. Its reach is wider than the per-row links: a connector the catalog holds by service id alone carries a null install_url and is still installed by this link, so never read a null install_url as "cannot be installed". When the answer offers several connectors to install, hand the user install_all_url and do NOT list the per-connector install_url links beside it — the one link IS the whole offer, and a table of links beside it puts the reader back through several sign-ins. One link carries at most 10 connectors, and install_all_omitted names the service ids it left out, so offer those rows their own install_url. install_all_url is null when the result offers nothing to install. It is null too wherever the result names no set the user has chosen: the unfiltered catalog and the whole bank domain never carry the link, and a name search or an accounting or invoicing domain carries it only while the WHOLE result fits in one link and this page holds all of it — past that the link would stand for whichever rows the page happened to carry. from_selection always carries the link, however many vendors were picked, because the user named each one. Where install_all_url is null, the rows' own install_url links ARE the offer: list them, and never announce a batch link the result does not carry.
Paging: page with offset and page_count, never with the length of connectors. On the first page of an unsearched browse the workspace's ALREADY CONNECTED connectors are prepended so the catalog's ordering cannot bury them past any page you would ask for — so connectors can be longer than the page it came from, and page_count is the catalog window's own length. Advance by offset + page_count; total counts every matching connector across all pages. Those prepended rows carry is_connected true (or a to_configure/disabled state), so a workspace's live tools are visible without paging for them.
Scoping: pass kind ("bank" | "accounting" | "invoicing") to get only the connectors serving that domain — the whole set, server-ordered, including the long tail of bank institutions. Pass kind: "upload_surface" for the places invoices ARRIVE — mailboxes, messaging apps, file drives. Pass kind: "storage" for the drives Well FILES INTO — Google Drive, Dropbox, OneDrive — the step that asks where Well should write the documents it collects. Neither of those two is a financial domain: the server resolves each from the connectors' own display categories, so read the rows it returns and never re-derive the set from category_id yourself. They are opposite DIRECTIONS on the same drives, so a "storage" row carries direction "output" and no data_domains, and its is_connected reports the workspace's own file-drop connection, never the drive's separate invoice-source connection. Pass q to name-search the full catalog. Omit all of them for the curated, matched-first view. Use well_list_connector_tools for a live connection's actions.
Country: pass country (ISO 3166-1 alpha-2, e.g. "FR") on a connect-a-bank step so the banks that serve the company's country sort first, then the ones that serve its region, then the rest. It reorders the page only — no row is dropped, and a country the catalog carries no coverage for is left in place — so a bank the user names is still found with q. Take the country from the workspace identity; omit it when the country is unknown, and the order is unchanged.
Pass from_selection: true for the connect step that FOLLOWS a vendor pick. It returns the connectors behind the counterparties the user picked on the missing-invoices card in this conversation, and NOTHING else: only the picked vendors' connectors, and of those only the ones that can bring an invoice in (reason "picked_vendor"). It offers no accounting or invoicing tool the user did not pick: that offer belongs to its own step, scoped with kind. When the pick leaves no row, the list is empty and the card is not worth drawing. ⚠️ This tool draws its card on EVERY call, the empty one included, so never call it with from_selection to find out whether the pick has a connector behind it. Ask well_get_worklist_status({ worklist: "invoice_sources_for_pick" }) first: it draws nothing, and it reports how many of the picked vendors carry a connector that can bring an invoice in. Make the from_selection call only when that count is above zero. An ABSENT count is not a zero: the probe answers success: false when it could not read the pick at all, so retry it rather than reading its silence as a vendor with no connector. row_count reports the same number back on this result. picked_vendors_filtered counts the picked vendors' connectors that were dropped for bringing no invoices in: when it is above zero, say a filter ran rather than letting a short card read as a pick nobody made. It takes no q and no kind: those browse a catalog, and this names a set already decided. An empty list means this conversation holds no pick for this workspace, or no picked counterparty matched a connector.
Every result carries scope — "catalog", one of the three domains, "upload_surface", "storage", or "picked_vendors" — naming what the list IS. The card words its title from that field, so a caller must not describe the result as a domain the scope does not name.
| Name | Required | Description | Default |
|---|---|---|---|
| q | No | Name search across the full catalog (e.g. a specific bank). Omit for the curated, matched-first view. | |
| kind | No | Scope the card server-side. Three financial domains: "bank" (every bank and neobank, including the long tail of open-banking institutions, plus the platforms that hold an account like Qonto and Pennylane — never a payroll or billing tool that merely reports transactions), "accounting", and "invoicing". Plus two scopes the server resolves from display categories rather than from a financial domain: "upload_surface", the places invoices ARRIVE (mailboxes, messaging apps, file drives), and "storage", the drives Well FILES INTO (Google Drive, Dropbox, OneDrive). The last two are opposite directions on the same drives, so a workspace can hold both connections and each is offered on its own card. Use this for a connect-a-bank, a connect-where-invoices-arrive or a connect-where-Well-files step instead of filtering the default view yourself. Omit for every connectable connector. | |
| limit | No | Max connectors to return (1-100, default 50). | |
| title | No | Heading for the connect card shown to the user, OVERRIDING the wording the card otherwise derives from the scope/kind. Use it to frame the step in its flow (e.g. "Connect your accounting tool for the close"). At most 120 characters. Omit to keep the default wording for the requested kind. | |
| offset | No | Number of connectors to skip, for paging (default 0). | |
| country | No | The company's country as an ISO 3166-1 alpha-2 code (e.g. "FR"). When set, the connectors that serve that country sort first, then the ones that serve its region, then the rest — the order only, no row is dropped. Use it on a connect-a-bank step so the banks that fit the company appear first; take it from the workspace identity's country. Omit when the country is unknown, and the order is left unchanged. | |
| subtitle | No | Supporting line under the connect card's heading, OVERRIDING the scope-derived subtitle. At most 240 characters. Omit to keep the default wording for the requested kind. | |
| workspace_id | No | Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did. | |
| from_selection | No | Scope the card to the connectors behind the counterparties the user picked on the missing-invoices card in this conversation, each installable one pre-checked. Use it for the connect step that FOLLOWS a vendor pick. It returns those vendors' connectors and NOTHING else: a picked connector that brings no invoices in is dropped, and no accounting or invoicing tool the user did not pick is offered here: that offer is its own step, scoped with `kind`. Cannot be combined with `q` or `kind`: those name a catalog to browse, and this names a set already decided. Returns an empty list when this conversation holds no pick for this workspace, or when no picked counterparty matched a connector. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| include_unsent_counts | No | Add the top-level `unsent_document_counts`: every tool Well forwards this workspace's documents to, each with how many documents it has not received yet. That array is the whole answer — it holds every one of the workspace's outbound connections, whatever catalog page was requested, and the rows carry no count at all. Off by default, because it costs an extra read. The figure is the workspace's whole backlog, not a period's. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| limit | No | The page size that was REQUESTED. The catalog may return fewer. |
| scope | No | What this result is a list OF: the requested kind, the picked vendors' connectors, or the curated catalog when neither was asked for. The card words itself from this, so it never describes the rows as a domain that was not requested. |
| total | No | Every connector matching the query, across all pages — NOT the length of `connectors`. |
| offset | No | How many catalog rows this page skipped. |
| success | Yes | |
| next_step | No | What to do with the card this result renders. Added by the dispatcher when the card asks for a click. |
| row_count | No | How many rows this result puts ON THE CARD, prepended rows included. `0` on a `picked_vendors` scope means the pick has no connector behind it that can bring an invoice in: the card carries nothing to tick, so say so in half a sentence and move on. Not a paging cursor — `page_count` is. |
| connectors | No | |
| page_count | No | The catalog page's own length, and the ONLY safe paging cursor: advance by `offset + page_count`. `connectors` can be LONGER — on the first page of an unsearched browse the workspace's already-connected rows are prepended so they cannot be lost to the catalog's ordering — so paging by the array's length silently skips exactly that many catalog rows on every later request. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| install_all_url | Yes | One link that installs every installable connector in this result. Null when the result offers nothing to install, or when its scope names a set the reader has not chosen. When it is non-null it is the ONLY install link the answer offers — do not list the rows' own install_url beside it. When it is null, the rows' own install_url is the offer instead. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| install_all_omitted | Yes | The service ids install_all_url could not carry, because one link names a bounded number of connectors. Offer these rows their own install_url instead of promising the batch link covers them. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
| unsent_document_counts | No | Every tool this workspace forwards documents to, each with the documents it has not received yet, biggest backlog first. Present only when `include_unsent_counts` was set. THIS IS THE ONLY PLACE THE BACKLOG IS REPORTED: it is not a page, it carries the workspace's whole set of outbound connections whether or not the requested catalog page holds their rows, and the catalog rows carry no count. An empty array means the workspace forwards to nothing; an absent field means nothing was measured. `unsent_document_count_is_upper_bound: true` means a document filter applies to that connection, so the count is a maximum and reads as `up to <n>`. Every count is read off Well's own forward record, so it measures what Well wrote down rather than what the tool confirmed. Name a tool by its `name`. An entry reading `0` is a tool that is up to date: say nothing about it. |
| picked_vendors_filtered | No | How many of the picked vendors' connectors were dropped for bringing no invoices in. Present on the `picked_vendors` scope only. Above zero means the card is SHORTER than the pick: say that those vendors' tools cannot deliver an invoice, rather than letting the gap read as a pick the user never made. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark the tool readOnly and non-destructive, so the bar is lower; the description adds critical behavior beyond those hints: 'This tool draws its card on EVERY call, the empty one included', it waits on the turn that drew it, and it explains status semantics like to_configure/disabled and the server-side resolution of 'degraded'. This materially changes how an agent should sequence calls.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long, but it is front-loaded with the highest-stakes warnings (card rendering, silent alternative, wait protocol) and organized into scoping, paging, per-field, and top-level-field sections. Some admonitions repeat, so it is not a model of brevity, but nearly every sentence carries operational weight given the tool's complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with 11 optional parameters, complex connection-status semantics, top-level fields, paging behavior, and sibling interactions, the description is exhaustive: every status, null case, scope, and edge condition is covered. An agent has enough to call it correctly and interpret the result without external knowledge.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 flow-level meaning beyond the schema: from_selection is the step that FOLLOWS a vendor pick, country reorders but never drops rows, kind: storage is the output direction versus upload_surface, and include_unsent_counts exposes the whole backlog independent of the page. These are not inferable from the parameter names alone.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Description opens with a specific verb and resource: 'List the connectors a workspace can install AND everything it has already connected, each with a one-click install deep link.' It names the card-drawing side effect and distinguishes itself from well_get_connector_coverage and well_list_connector_tools, so an agent can tell it apart 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use this tool versus alternatives: 'Do NOT read workspace_connectors records to work out connection coverage; this tool is that answer', routes silent checks to well_get_connector_coverage, vendor-pick probes to well_get_worklist_status, and live-connection actions to well_list_connector_tools. It also instructs the agent to call well_wait_for_selection in the turn that drew the card.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_list_connector_toolsList connector toolsARead-onlyInspect
Discover the actions a connected provider exposes (e.g. "what can I do with Attio?").
WORKFLOW:
well_list_connectors() → pick the ENABLED provider (connection_status: "enabled") and read its workspace_connector_id directly off the row.
well_list_connector_tools({ workspace_connector_id }) → the actions that provider offers (name + description + input schema).
well_invoke_connector_tool({ workspace_connector_id, tool, args }) → run one, shaping args from the input schema returned here.
Use this whenever you don't already know a connector's tool names — never guess them.
Every response also carries reconnect_url: a deep link to the connector's setup page in the web app. When success is false or status is "need_reconnect" (the provider's token is stale/revoked, so no tools come back), give the user reconnect_url so they can re-authenticate the connector. Surface it as a clickable link; never invent connector URLs.
| Name | Required | Description | Default |
|---|---|---|---|
| workspace_id | No | Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| workspace_connector_id | Yes | The connected provider's workspace_connector_id (from well_list_connectors). |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| tools | No | |
| total | No | |
| status | No | |
| success | Yes | |
| usage_notes | No | |
| reconnect_url | No | |
| connector_slug | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and destructiveHint=false, covering the safety profile. The description adds meaningful behavioral context: it explains the response carries reconnect_url, describes the failure mode (success false or status 'need_reconnect' meaning stale/revoked token), and instructs to surface the URL as a clickable link and never invent connector URLs. 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is appropriately sized for a tool that is part of a multi-step workflow; it is front-loaded with the purpose and uses a clear WORKFLOW section. Every sentence earns its place: the use condition, the reconnect_url handling, and the warning not to invent URLs all convey necessary guidance without fluff.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool has an output schema, the description doesn't need to detail return values, but it does: it explains the response includes tool names, descriptions, input schemas, and reconnect_url. It also covers error handling for reconnection. Combined with fully documented parameters and safety annotations, 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.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has 100% description coverage, so the baseline is 3. The description only mentions workspace_connector_id in the workflow, and the schema already states it comes 'from well_list_connectors'. The optional parameters workspace_id and conversation_id are fully documented in the schema, and the description adds no extra meaning beyond that.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it discovers the actions a connected provider exposes, with an example ('what can I do with Attio?'). It distinguishes itself from well_list_connectors (lists connectors) and well_invoke_connector_tool (invokes a tool) by naming them in the workflow, 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides an explicit workflow: first call well_list_connectors, then this tool, then well_invoke_connector_tool. It states 'Use this whenever you don't already know a connector's tool names — never guess them,' which gives a clear when-to-use rule and implies 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.
well_list_counterpartiesList counterpartiesARead-onlyInspect
List the workspace's counterparty companies and how each one is CATEGORIZED — the company-level industry labels a counterparty carries. Use it for "which suppliers have no category?", "what industries are my counterparties in?", and before categorizing a counterparty so you name real ids instead of guessing.
Name a scope, and say whether to keep only the ones missing a category:
periods: [{ calendar_year, calendar_month }, …](1-12): the counterparties whose invoices those months are still missing, categorized ones included, each row tagged with its month and carryingtx_count,base_total_amountinbase_currency, andsuggested_retrieval. Every month must have ended.periodsPLUSuncategorized_only: true: the same months, keeping ONLY the counterparties that carry no category. Use this whenever the question is which of a period's suppliers still need one, and whenever a step asks the user to categorize them: the categorized ones are not the work, and listing them buries it.uncategorized_only: truealone: a WORKSPACE-WIDE sweep for every counterparty that carries no category, no month involved. Returns 50 rows per page plustotal_count;tx_count,base_total_amountandsuggested_retrievalare null because the call names no period. Whennext_cursoris not null the sweep has more counterparties: call again withcursorset to it to read them. It is a POSITION, not a row offset, so categorizing the rows of one page never hides the rows of the next. Only this sweep pages:cursoris refused besideperiods.missing_ledger_only: truealone: the LEDGER-ASSIGNMENT worklist — the counterparties that have a bank transaction and still need a ledger account (COA) default set for the direction their transactions take. Its rows ride inledger_rows, notrows, each carryingneeds_payable/needs_receivableand the AP/AR default it holds now; a needed slot is empty, assigned from the chart of accounts. This is a DIFFERENT question from categorization: it assigns a ledger account, not an industry label. Set a default withwell_update_company({ account_payable_default_id | account_receivable_default_id }); read the account ids withwell_list_ledger_accounts. It returns the first 500 counterparties needing a default, so a worklist that fills 500 (total_countequal torow_countat 500) is a FLOOR: assign those and read the scope again for the rest. It is its own scope — never combine it withperiods,uncategorized_only, orcursor.
COST: the period form has no batch endpoint, so each named month is a separate read of that month's spend. Ask for the months the user actually named, not a whole year "to be safe".
Every row carries categories ([{ category_id, name }]) and is_categorized. categorized_count and uncategorized_count count the COUNTERPARTIES OF THE SCOPE, once each however many months they appear in, not the rows returned. Under uncategorized_only the result lists the uncategorized ones alone while categorized_count still counts the ones it withheld, so the two together are the period's coverage and uncategorized_count is the work left. Report both: naming the listed rows as the period's whole counterparty set overstates how much is uncategorized.
TO SET a counterparty's categories, call well_update_company({ company_id, category_ids: [...] }) — that field REPLACES the company's whole set. Read the available labels first with well_query_records({ root: "categories", whereClause: { category_type: { _eq: "company" } } }): that is the company-category catalog. It has no curated allowlist — the labels are minted during enrichment — so pass ids from it rather than inventing a taxonomy.
suggested_retrieval is derived from the PROVIDER match, not from the category. Categorizing a counterparty does not change it; do not tell the user otherwise.
This tool only reads. It categorizes nothing, mints no task, connects nothing and fetches no invoice.
No workspace read is needed first: the workspace is resolved from the caller's authorized token, same as every other well_* tool.
When the user asks to see, list or fix these rows, call this tool directly: its card is the answer. ⚠️ This tool draws its card on EVERY call, the empty one included. So when nobody asked for the list and you only need to CHECK whether anything is left (the first pass of a gate, or a re-check after a repair), call well_get_worklist_status({ worklist: "counterparties_to_categorize", periods }) first, with the same scope you would pass here. It draws nothing. Call this tool after it only when it answers open: true.
| Name | Required | Description | Default |
|---|---|---|---|
| cursor | No | The next page of the workspace-wide uncategorized sweep: pass back the `next_cursor` the previous call returned. Only that sweep pages, so this needs `uncategorized_only: true` and NO `periods`, because a period scope returns every month it covers in one call. | |
| periods | No | The calendar months whose counterparties to list, 1-12. Each month costs one separate read of that month's spend. Duplicates are refused. | |
| workspace_id | No | Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| uncategorized_only | No | Keep only the counterparties that carry no industry category. WITH `periods`: the uncategorized counterparties OF those months. Use it whenever the question is which of a period's suppliers still need a category. WITHOUT `periods`: a WORKSPACE-WIDE sweep for every uncategorized counterparty, 50 rows per page plus the total, with a `next_cursor` for the page after this one. | |
| missing_ledger_only | No | A workspace-wide sweep for the counterparties that have a bank transaction and still need a ledger account (COA) default set for the direction their transactions take — the ledger-assignment worklist. It is its OWN scope: never pass it with `periods`, `uncategorized_only`, or `cursor`. Each row carries `needs_payable`/`needs_receivable` and the AP/AR defaults it holds now (a needed slot is empty), and rides in `ledger_rows` rather than `rows`. |
Output Schema
| Name | Required | Description |
|---|---|---|
| mode | No | Which SCOPE the call asked for; the row fields that are populated follow from it. `periods` whenever the call named months, whether or not it also filtered to the uncategorized ones. `missing_ledger_only` returns its rows in `ledger_rows`, not `rows`. |
| rows | Yes | |
| error | No | |
| hints | No | |
| success | Yes | |
| next_step | No | What to do with the card this result renders. Added by the dispatcher when the card asks for a click. |
| row_count | No | |
| ledger_rows | No | |
| next_cursor | No | The uncategorized sweep's next page: pass it back as `cursor`. Null when this page ends the sweep, absent on the periods scope, which pages nothing. |
| total_count | No | Counterparties MATCHING the call, before the row cap, so a capped sweep says what it left out. On the periods scope: the DISTINCT counterparties `rows` names, and a multi-month call lists one counterparty on one row per month, so row_count can exceed it. Under `uncategorized_only` it counts the uncategorized ones alone; the scope's whole population is `categorized_count` plus `uncategorized_count`. Under `missing_ledger_only` it is the worklist actually returned and equals `row_count`: that scope reads the first 500 counterparties needing a default, so a worklist that fills 500 is a FLOOR, not a complete count. Assign those and read the scope again for the rest. |
| workspace_id | No | |
| base_currency | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| periods_covered | No | The months the result covers, oldest first. |
| categorized_count | No | COUNTERPARTIES of the scope that carry at least one category, counted over the whole scope, not over `rows`, and counted once however many months a counterparty appears in. Under `uncategorized_only` these are exactly the counterparties the result withheld, so a non-zero figure beside rows that are all uncategorized is the coverage, not a contradiction. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| uncategorized_only | No | Whether `rows` holds ONLY the counterparties that carry no category. True on the workspace-wide sweep and on a period scope the call filtered. When true, `categorized_count` counts counterparties the result did NOT list. |
| uncategorized_count | No | COUNTERPARTIES of the scope that carry none, counted the same way. This is the outstanding work; with `categorized_count` it is the scope's whole population. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Discloses significant behavioral traits beyond annotations: the card-drawing side effect ('draws its card on EVERY call, the empty one included'), the cost model ('each named month is a separate read of that month's spend'), auth/workspace resolution ('resolved from the caller's authorized token'), and a misleading-signal caveat ('suggested_retrieval is derived from the PROVIDER match, not from the category'). It is consistent with annotations (readOnlyHint=true matches 'This tool only reads'); 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long (~700 words) but earns its length given 6 optional parameters, four exclusive modes, exclusivity rules, and a cost caveat. It is strongly front-loaded (purpose then usage in the first two sentences) and uses clear scannable markers (COST, ⚠️, mode bullets). Minor rhetorical padding ('the categorized ones are not the work, and listing them buries it') and slight repetition of scope-exclusivity keep it from a 5.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's genuine complexity (four modes, pagination, exclusivity constraints), the description is complete: it covers every mode combination, count semantics ('categorized_count... counts the COUNTERPARTIES OF THE SCOPE, once each'), return-shape differences (ledger_rows vs rows), and paging continuation. With an output schema present, the extra return-value detail is a bonus rather than a necessity, and nothing an agent needs to call the tool correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Despite 100% schema coverage, the description adds composition semantics the schema cannot convey: three mutually exclusive modes, 'cursor is refused beside periods', 'Every month must have ended', the 500-row floor for missing_ledger_only, and cursor semantics ('It is a POSITION, not a row offset'). It also explains what each mode returns (ledger_rows vs rows, next_cursor), going well beyond the baseline for full schema coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource — 'List the workspace's counterparty companies and how each one is CATEGORIZED' — with a clear scope (company-level industry labels). It explicitly differentiates from siblings: the ledger-assignment mode is called 'a DIFFERENT question from categorization', and well_get_worklist_status is flagged as the check-only alternative. An agent can distinguish this tool from well_set_transaction_category or well_list_uncategorized_window 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use signals with user-question templates ('which suppliers have no category?', 'what industries are my counterparties in?'), a when-not rule ('when nobody asked for the list and you only need to CHECK... call well_get_worklist_status first'), and clear routing to alternatives (well_update_company for setting categories, well_list_ledger_accounts for COA ids). It also tells the agent when to prefer the uncategorized_only variant ('the categorized ones are not the work').
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_list_ledger_accountsList ledger accountsARead-onlyInspect
List the workspace's chart of accounts (COA) — every ledger account a counterparty default or a transaction can be assigned to. Use it to name a real ledger_account_id in a write instead of guessing one.
Each account carries id (the ledger_account_id the writes take), name, and code (its account number, e.g. "401" for a vendor or "411" for a customer under the FR PCG) where the account has one.
truncated: true means the chart holds MORE accounts than this page carries; read the rest with well_query_records on the ledger_accounts root.
success: false means the chart could not be read, NOT that the workspace has none. An empty accounts on a failed read is unknown, never "no chart of accounts".
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max accounts to return; the chart is capped either way. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| accounts | Yes | |
| returned | No | |
| truncated | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description goes well beyond the readOnlyHint, openWorldHint, and destructiveHint annotations. It explains the error semantics: 'success: false means the chart could not be read, NOT that the workspace has none', the meaning of truncated: true and how to fetch more, and the workspace selection behavior (will not guess). These are important behavioral disclosures not encoded 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but well-structured, front-loading the core purpose before covering fields, pagination, error semantics, and usage. Every section adds value; some redundancy exists (e.g., repeated workspace guidance), but it remains focused and readable. A solid 4.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the output schema exists (so return values need not be explained), the description covers the critical operational context: pagination via truncation, error semantics, workspace selection, and mapping to real ledger_account_ids. Nothing an agent requires 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.
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 non-trivial context for workspace_id, explaining when it is required and that the read will not guess which workspace to use. It also subtly reinforces the limit param ('the chart is capped either way'). This extra guidance justifies a 4.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool lists the workspace's chart of accounts, and explains its purpose: to obtain a real ledger_account_id for writes. It differentiates from siblings by referencing well_query_records for pagination and by focusing on the COA listing role, while mentioning what the accounts carry (id, name, code). This is a specific verb+resource with clear scope.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to call directly (when the token authorizes one workspace) and when to pass workspace_id (when it authorizes several). It also clarifies the relationship to well_query_records for reading additional pages of the chart. This gives clear when-to-use guidance and distinguishes from alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_list_member_candidatesList member candidatesARead-onlyInspect
List the teammates a workspace can invite, exactly as the Well app's invite card shows them. Use it before well_invite_members, and for "who can I invite to this workspace?".
Returns candidates, each with person_id, name, email, avatar_url, a state (active already has access, pending was invited and has not accepted, not_member can be invited), and a source (detected shares the workspace owner's corporate email domain, provided was named in person_ids or resolved from the assigned gap owners). Never invite a candidate whose state is active. Alongside them it returns targets — this workspace plus any workspace group you belong to, each an option for where the invite lands — roles (admin or member, with a hint), and me_person_id so you never offer to invite the caller.
Three ways to source the candidates:
Default: the detected same-domain teammates who hold no membership.
person_ids: resolve specific people you already hold the ids for, with their membership state. Setinclude_detectedfalse to return only those.from_assigned_gaps: true: resolve the owners of the settled expense transactions still missing a supplier invoice for the period, server-side, with their membership state — the invite step of a close or fetch flow uses this so it never depends on remembering who was assigned on the owner card. It returns only those owners (the detected teammates are omitted). Name the period ONE way —{ calendar_year, calendar_month }or{ fiscal_year, fiscal_period }— or name no period to use the months selected on the period card this session. Every month must have ended.
⚠️ WAIT ON THE CARD IN THE TURN THAT DREW IT. Write your one line for the user FIRST, then call well_wait_for_selection({ kind: "invite_ack" }), which this result's next_step also states. The card's own footer sends the invitations and writes the acknowledgement, so never call well_invite_members yourself after a click. The outcome the click carries says which button it was: "done" sent the invitations, "keep_for_later" set the step aside. Both end the step.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| person_ids | No | Person ids to resolve with their membership state (the `provided` source). | |
| fiscal_year | No | Fiscal year (the calendar year the workspace's fiscal year STARTED in). `from_assigned_gaps` only. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| calendar_year | No | Calendar year, e.g. 2026. `from_assigned_gaps` only. | |
| fiscal_period | No | Fiscal period, 1-12. `from_assigned_gaps` only, paired with `fiscal_year`. The adjustment period (13) is refused. | |
| calendar_month | No | Calendar month, 1 = January … 12 = December. `from_assigned_gaps` only, paired with `calendar_year`. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| include_detected | No | Omit the detected same-domain teammates when false. Defaults to true. | |
| from_assigned_gaps | No | Resolve the candidates from the owners of the period's missing-invoice gaps, server-side, instead of `person_ids`. The detected teammates are omitted, and `include_detected` is treated as false. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| roles | Yes | The assignable roles, `admin` or `member`, each with a one-line hint. |
| success | Yes | |
| targets | Yes | Where an invite can land: this workspace, plus any workspace group the caller belongs to. |
| next_step | No | What to do with the card this result renders. Added by the dispatcher when the card asks for a click. |
| candidates | Yes | |
| me_person_id | Yes | The caller's own person id, so the card never offers to invite them. |
| workspace_id | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, and the description goes well beyond: it details the return structure (candidates, targets, roles, me_person_id), the three sourcing modes, state semantics, and the requirement to wait on the card and call well_wait_for_selection. It also clarifies that the read does not guess the workspace when multiple are authorized.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Long but efficient—every sentence carries operational guidance. Organized into clear paragraphs (purpose, returns, sourcing modes, card interaction, authorization) and front-loads the primary purpose. No redundant phrasing; each sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with 9 parameters, an output schema, and a multi-step card interaction, the description covers everything: return fields, state/source semantics, sourcing modes, period rules, conversation_id handling, authorization, and the card footer's role. 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.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Even though the schema covers all 9 parameters, the description adds critical interaction semantics: from_assigned_gaps overrides include_detected, period must be named one of two ways, conversation_id must be passed back, and the default behavior of include_detected. This is meaning beyond the schema's individual descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'List the teammates a workspace can invite' and ties it to the Well app's invite card. It also names the sibling it is not: 'Use it before well_invite_members', making it unmistakable which action this covers.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use it (before well_invite_members, for 'who can I invite') and when not to (never call well_invite_members after a click). It also gives conditional guidance for workspace_id based on token authorization and explains the card wait step.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_list_missing_invoice_ownersList missing-invoice ownersARead-onlyInspect
List the settled expense TRANSACTIONS a past period is still missing a supplier invoice for, one row per line, each with its current owner SET. Use it for "who owes the missing invoices?" and as the input to well_assign_missing_invoice_owners.
Each row reports its TRANSACTION owner SET. An empty owners set means no transaction owner set was found; it does not prove that no card rule or other legacy owner exists. The bucket is no_owner_set, assigned_to_me, or assigned_to_others, computed from that set against the calling person. This lists the SAME missing invoices well_list_missing_invoices shows, but flattened to lines you can assign; there is no per-card grouping and no scope: "card".
Name the period ONE way: { calendar_year, calendar_month }, { fiscal_year, fiscal_period }, or periods: [...] for several months (1-12), or name NO period to use the months selected on the period card in this conversation. Every month must have ended.
Each row carries transaction_id (pass it to well_assign_missing_invoice_owners), date, description, counterparty (name, id, and logo when a provider was matched), amount, currency, and base_amount. Rows with no owner set come first, then the caller's own, then those owned only by others; no_owner_set_count, assigned_to_me_count, and assigned_to_others_count summarize the split over the returned rows.
The rows per counterparty are a BOUNDED sample (sampled: true), so row_count may be fewer than transaction_count — the window's true total — and transactions_omitted is the difference. Use it to assign owners, not to count a period's total gaps; well_list_missing_invoices carries the full per-counterparty totals.
This tool reads the user's data and changes none of it.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| periods | No | Several calendar months in one call, 1-12. Each month costs one separate read, so name only the months you need. Duplicates are refused. | |
| fiscal_year | No | Fiscal year (the calendar year the workspace's fiscal year STARTED in). | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| calendar_year | No | Calendar year, e.g. 2026. | |
| fiscal_period | No | Fiscal period, 1-12. The adjustment period (13) is refused: it has no calendar month. | |
| calendar_month | No | Calendar month, 1 = January … 12 = December. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| sampled | No | True always: each counterparty's rows are a bounded sample, so a counterparty whose every gap fell outside the sample is under-represented. Use it to assign owners, not to count a period's total gaps. |
| success | Yes | |
| next_step | No | What to do with the card this result renders. Added by the dispatcher when the card asks for a click. |
| row_count | No | Transaction rows returned in `transactions` (a bounded sample per counterparty). |
| me_person_id | No | The calling person, against which each row's `bucket` is computed; null when the token carries no person. |
| transactions | Yes | The missing-invoice lines, with no owner set first, then the caller's own, then those owned only by others. |
| base_currency | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| periods_covered | No | The months the result covers, oldest first. |
| transaction_count | No | Total missing-invoice transactions the window holds across all counterparties, sampled or not. |
| no_owner_set_count | No | Returned rows with no transaction owner set. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| assigned_to_me_count | No | Returned rows whose transaction owner set includes the caller. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| transactions_omitted | No | Missing-invoice transactions the window holds beyond the returned sample (`transaction_count` − `row_count`). |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
| assigned_to_others_count | No | Returned rows whose transaction owner set includes only other people. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and destructiveHint=false, and the description reinforces this with 'This tool reads the user's data and changes none of it.' Beyond annotations, it discloses important behavioral traits: empty owners set does not prove no legacy owner exists, rows are a bounded sample (sampled: true) so row_count may be less than transaction_count, and the period-card fallback when no period is named. It also explains the ordering of rows and the meaning of the bucket values, which is exactly the kind of context 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but every paragraph earns its place: purpose, row semantics, period selection, output fields, sampling caveat, and workspace routing. It is front-loaded with the core purpose and the key distinction from the sibling. It could be tightened slightly, but for a tool with this many behavioral caveats the length is justified.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity, the rich output schema, and the 100% schema coverage, the description covers everything an agent needs to call it correctly: what it returns, how rows are ordered, what the counts mean, the sampling caveat, the period-card fallback, and the workspace_id rule. The output schema exists, so the description need not enumerate return fields, but it still names the key fields and their purpose. Nothing critical is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 every parameter. The description adds value by explaining the period-naming alternatives as a coherent choice ('Name the period ONE way: ... or name NO period to use the months selected on the period card'), and by clarifying that every month must have ended. It also tells the agent to pass transaction_id to the assignment tool, which adds meaning beyond the schema. A 4 is appropriate because the description complements rather than merely repeats the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('List'), a precise resource ('settled expense TRANSACTIONS a past period is still missing a supplier invoice for'), and the output shape ('one row per line, each with its current owner SET'). It also explicitly distinguishes itself from the sibling well_list_missing_invoices ('same missing invoices ... but flattened to lines you can assign; there is no per-card grouping and no scope: "card"'), so an agent can tell them apart 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-to-use ('who owes the missing invoices?') and names the intended downstream sibling (well_assign_missing_invoice_owners). It also gives clear exclusions: use well_list_missing_invoices for full per-counterparty totals, and it explains when to pass workspace_id (multi-workspace tokens) versus calling directly (single-workspace tokens). This is unusually complete routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_list_missing_invoicesList missing invoicesARead-onlyInspect
List the supplier invoices a past period is still missing — the settled spend whose invoice has not been collected, one row per counterparty, exactly as the Well app's expense-invoices card shows them. Use it for "which invoices am I missing for ?" and as the input to fetching them.
Name the period ONE way: { calendar_year, calendar_month } (the calendar month, e.g. June 2026 → 2026, 6), { fiscal_year, fiscal_period }, or periods: [{ calendar_year, calendar_month }, …] for SEVERAL months in one call (1-12), or name NO period at all to use the months the user selected on the period card in this conversation (well_list_periods → the user clicks → well_switch_workspace records them). With no period named and no months selected, the call refuses and tells you to run the period step first. Every month must have ended: a current or future month is refused, and so is the adjustment period (13). Duplicate months are refused.
COST: there is no batch endpoint, so each named month is a separate read of that month's spend. Ask for the months the user actually named, not a whole year "to be safe".
Returns rows, ONE per counterparty for the whole call, never one per month. Each row carries name, tx_count and base_total_amount in base_currency SUMMED over the months it covers, its own months array naming those months (each with that month's tx_count, base_total_amount, proof_task_id, acquisition_status and refusal_reason), and the route fields mode, available_modes, suggested_action, matched_provider_name and matched_connector_service_id, which the provider match resolves once per counterparty. NEVER list a counterparty once per month and never present its months as separate gaps: it is one supplier to chase, and one collection covers every month behind it. Name the months a row spans from its months array. The envelope's own months carries each month's totals (rows are NOT repeated there), periods_covered names the months read, and transaction_count, group_count and dropped_groups are totals across every month read. row_count counts the DISTINCT counterparties, so it is never the sum of the months' own row_count. dropped_groups counts the GROUPS that produced no row — party-less bank operations, unresolved counterparties, unnamed companies — never transactions, and bank_internal and unknown hold one group per month whatever they contain, so quote neither as a quantity of operations. unknown and unnamed_company ARE categorized expense spend still missing a supplier invoice, so an empty rows over a non-zero count is not a complete period; bank_internal alone is, since no supplier can invoice a party-less operation. The single-month fields calendar_year, calendar_month, fiscal_year, fiscal_period and period_label appear ONLY when the call named exactly one month.
Every row also carries transactions — the counterparty's own lines behind the row, each with date, description (the bank's remittance text), category, amount, currency and base_amount. amount is signed and stays in the transaction's own currency, so never add those together across a row; base_amount is the same line in base_currency, and the magnitudes of those DO add up to base_total_amount. The list is capped at 25 per row and transactions_omitted says how many the cap left out — quote that number instead of implying the list is complete.
mode is the ONE route the card suggests for that row: agent (a browser agent can collect it from the supplier portal), connect (connect the named service and Well fetches it), upload (the user supplies the file). available_modes lists every route the row offers instead of only the suggested one — agent and upload on every row, plus connect when the catalog holds a connector for the matched provider, so 2 or 3 entries. Present mode as the suggestion and available_modes as the choice.
Only CATEGORIZED expense transactions are considered — uncategorized spend is not listed, so poor categorization coverage under-reports the gaps; disclose the hints.
This tool reads the user's data and changes none of it. It does not mint tasks, start a close, connect anything, or fetch any invoice.
Call this directly — no other tool call is needed first (workspace is resolved from the caller's authorized token, same as every other well_* tool).
| Name | Required | Description | Default |
|---|---|---|---|
| periods | No | Several calendar months in one call, 1-12. Each month costs one separate read, so name only the months you need. Duplicates are refused. | |
| fiscal_year | No | Fiscal year (the calendar year the workspace's fiscal year STARTED in). | |
| workspace_id | No | Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did. | |
| calendar_year | No | Calendar year, e.g. 2026. | |
| fiscal_period | No | Fiscal period, 1-12. The adjustment period (13) is refused: it has no calendar month. | |
| calendar_month | No | Calendar month, 1 = January … 12 = December. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes | The card's rows, ONE per counterparty for the whole call, in the order the oldest month listed them, each naming the months it covers in `months`. A counterparty owing an invoice in several of the months read is one row, never one per month. |
| error | No | |
| hints | No | |
| months | No | Per-month totals, oldest first. |
| success | Yes | |
| next_step | No | What to do with the card this result renders. Added by the dispatcher when the card asks for a click. |
| row_count | No | Rows in `rows`, which is the DISTINCT counterparties the call found. Never the sum of the months' own `row_count`. |
| fiscal_year | No | Present only when the call named exactly one month. |
| group_count | No | Groups the reads returned before the card's projection. |
| period_label | No | Human-readable label of the period, e.g. "June 2026". Present only when the call named one month. |
| workspace_id | No | |
| base_currency | No | |
| calendar_year | No | Present only when the call named exactly one month. |
| fiscal_period | No | Present only when the call named exactly one month. |
| calendar_month | No | Present only when the call named exactly one month. |
| dropped_groups | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| periods_covered | No | The months the result covers, oldest first. |
| periods_requested | No | How many calendar months the call named. |
| transaction_count | No | Every transaction missing its invoice, across all groups and all months. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint annotation, the description discloses extensive behavioral detail: one row per counterparty rather than per month, no batch endpoint and per-month cost, categorized-only coverage, a 25-row cap with transactions_omitted, mode/available_modes semantics, and a clear statement that it does not mint tasks or fetch invoices. This goes far beyond the annotation's safety signal.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but densely organized into purpose, period naming, cost, output shape, per-row fields, modes, and side-effect guarantees. Every paragraph adds a distinct operational rule or edge case, and the core purpose is front-loaded in the first sentence. There is no filler or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity, the description is complete enough to call it correctly: it covers period selection, output semantics, edge cases like dropped_groups and unknown categories, field presence conditions, caps, and the read-only guarantee. Even with an output schema present, the description supplies critical interpretation rules the schema cannot convey.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is already 100%, but the description adds substantial meaning: period can be named one of several mutually exclusive ways, omitting it falls back to the user's period-card selection, each named month costs a separate read, and workspace resolution comes from the caller's authorized token. These semantics are not inferable from the schema alone.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a precise verb, resource, and scope: it lists supplier invoices a past period is still missing from settled spend, one row per counterparty, matching the Well app's expense-invoices card. This clearly distinguishes it from sibling read tools like well_sum_invoices or well_list_unposted_transactions.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly states when to use the tool ('which invoices am I missing for <month>?'), gives the exact input shapes, and describes refusal conditions (current/future months, adjustment period 13, duplicates, no period selected). It also tells the agent to request only the months the user named and to call the tool directly without prerequisites.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_list_periodsList periodsARead-onlyInspect
List the recent accounting months of the workspace, with each month's close status, its invoice-retrieval state, and the counts that describe how much work it holds. Use this to ask the user WHICH month or months to work on before any close, review, or month-scoped read — do not guess a month, and do not derive one from today's date yourself.
Each entry carries:
calendar_year / calendar_month: the month itself.
fiscal_year / fiscal_period: the same month in the workspace's fiscal calendar — this is the pair every close endpoint and close tool takes.
label: the month written out, e.g. "March 2026".
is_complete: the calendar month has ended. A still-accruing month is never a valid close target.
selectable: the month can be CLOSED. False for a month that has not ended, one already closed, one with nothing to close, and a December whose year-end close is not supported yet. Read this one for a close pick.
analyzable: the month can be REPORTED ON. True once the month has ENDED and while it remains inside the window the canvas endpoints serve; false for the month in progress, for a future month, and for one too far back. It does NOT ask for a close verdict, because a report reads transactions and an unchecked month still has them. Read this one for an analysis pick.
inspectable: the month can be LOOKED INTO. A reader can open its transactions, its missing invoices and its days. True for EVERY month that has begun, the month in progress included. False only for a month that has not begun. It reads no close verdict and no activity count, so a closed month, an empty month and a workspace with no accounting connector at all still have readable months. An empty month answers with an empty list, which is an answer. Read this one for a retrieval or review pick; every selectable month is also inspectable.
close_status: "closeable" (ready), "not_ready" (work remains), "closed" (already locked), "nothing_to_close" (no activity), or null when the workspace has no verdict for that month.
close_reason: the blocking reason behind the status, or null.
invoice_state: "missing_invoices" (at least one counterparty still owes a supplier invoice), "has_invoices" (checked, and nothing is missing), or "none" (no state: no activity, the month has not begun, or the check could not run). Never read "none" as "nothing missing".
missing_invoice_count: how many counterparties owe an invoice for the month — the rows
well_list_missing_invoiceswould return. 0 whenever invoice_state is "none", including when the check did not run.transaction_count: how many transactions the month holds, dated on the basis this purpose measures on.
analysiscounts onexecuted_at, the same columnwell_sum_transactionsranges, so a month's count and a reporting figure cover the same WINDOW. It is not the same row set, and must never be quoted as the figure's row count: the sum can also drop internal transfers and exempt categories on request, and it widens to a parent's granted transactions where this count does not. Read it as a presence signal for the month.closeandcollectcount on the books date,COALESCE(value_date, booking_date), which a transaction the bank has not booked does not carry — so a zero under those purposes means no BOOKED transaction, never an empty month.bank_transaction_count: the subset of transaction_count delivered by a connector the workspace actually BANKS with, meaning a bank, a neobank, or a treasury or spend platform whose product is an account. An accounting platform and a payment processor deliver transactions too, so transaction_count is NOT a bank signal. Only this field answers "has a bank fed this month". A transaction counts as not-bank when its source connector is unknown, or when that connector has since been disconnected, so a zero here never licenses skipping a bank-connection step.
unposted_invoice_count: invoices the month HAS that have not posted to the ledger. This is a posting gap, not a missing invoice — do not present it as one.
uncategorized_transactions: transactions in the month not yet categorized — the "help categorize" errand behind a not-ready close. Dated on the books, so it is ABSENT under
analysisrather than 0 — that purpose counts on execution and never measures this errand, and a 0 would read as "nothing left to categorize". It is also absent on a month the coverage read did not cover. Never read an absent count as "nothing left to categorize": say the month was not measured, or read it again.categorized_unposted_transactions: categorized transactions not yet posted to the ledger — part of the "review and book" errand. Dated on the books, absent under
analysis, and absent on an unmeasured month for the same reason.invoice_state / missing_invoice_count: the month's invoice-retrieval verdict and the count behind it. Both are ABSENT on a month a
analysislist skipped — that purpose bounds its invoice read by execution-dated activity while the errand is dated on the books, so the two disagree and a "none" there would be a claim nothing measured. Absent is not "owes nothing"; read it from acloseorcollectlist.days: the DAYS of the month that carry a retrieval state, ascending, each
{ day, state }over the same vocabulary as invoice_state. A day is "missing_invoices" when it holds settled expense spend still missing its supplier invoice, and "has_invoices" when it holds activity and no such gap. Days with neither are OMITTED, so an absent day means "none".daysis empty for every month whose invoice_state is "none" — an unchecked month has no day the tool can call clean — and it is empty for EVERY month on apurpose: "analysis"call, whatever that month's invoice_state, because the reporting axis paints no day. On that purpose an emptydaystherefore says nothing about invoice coverage, and neither does an absent invoice_state. This is calendar detail for a picker to paint; quote the month's own counts, not a day list, when answering in prose.analysis_days: present ONLY for a
purpose: "analysis"call — the DAYS and whether a breakdown can name what each holds, ascending, each{ day, state }over "categorized" / "uncategorized" / "neutral". An "uncategorized" day holds a transaction with no category. It still COUNTS toward a burn total, which filters on no category at all; a cost breakdown just reports it as uncategorised rather than under a named category. Never say a total is short because of it. Unlike the other two axes a quiet day IS listed, as "neutral". Calendar detail for the reporting picker.close_days: present ONLY for a
purpose: "close"call — the DAYS carrying a non-neutral close-readiness state, ascending, each{ day, state }over "posted" / "progress". A day absent from it is "neutral" (nothing to close). Calendar detail for the close picker, likedaysis for retrieval.
default_period is the oldest month that is ready to close, falling back to the oldest still in progress. Offer it as the default choice. It reads selectable, so it is null whenever no month in the window can be CLOSED, and a null one does not mean the window is empty: an inspectable month can still be worked on for invoice retrieval. On a purpose: "analysis" call it is instead the NEWEST analyzable month, because a figure describes the latest ended period and a month outside the reporting window would be refused by the endpoints that serve it.
PURPOSE: pass purpose: "close" when the user is closing the books, so the picker paints close readiness and each month carries its close_days and the categorize / review counts. Pass purpose: "analysis" when a REPORTING SKILL is already running and is choosing the month its figure will cover, so the picker offers only analyzable months and paints the CATEGORIZATION day axis: neither invoice coverage nor close readiness is the decision being made, but an uncategorized day is one a breakdown cannot attribute. A user who merely mentions a report, a burn figure or a cost breakdown is NOT the trigger — naming one of those is phrasing, and phrasing never sets this field. Example: "What months do you have for me? I'm trying to work out my average burn." is a plain listing request that names a reason — it is NOT a reporting skill calling for its own period pick, so this call OMITS purpose. Only a caller that IS the reporting flow itself (an avg-burn/cost-structure/cash-flow-waterfall skill run, already past its own gates, now needing the month to compute against) passes "analysis" — never derive it from words in the user's own message, no matter how closely they match a report. Omit it (or purpose: "collect") for invoice retrieval, the default. This is the calling skill's intent — set it from the flow, never from the user's phrasing.
WINDOW: by default the months most recent calendar months, ending with the current one. Pass year instead to get ONE calendar year in full — all twelve of its months, December back to January — which is how you reach a year the recent window does not cover, backwards or forwards. navigable_years reports the range year is answered for.
A year ahead of today comes back in full and every month of it is selectable: false and inspectable: false with close_reason "period_not_ended": books close on a month that has ENDED, and a month that has not begun holds nothing to read. Show such months when the user asks to look ahead, and say why they cannot be picked. Never omit them.
COST: the invoice state is read per month from a separate endpoint, so a wide window costs one extra read for every month that holds activity, plus one day-coverage read per calendar year those months touch. A purpose: "analysis" call pays the same day-coverage read, dated on the execution basis, because it paints the categorization day axis. Ask for the months the user needs, not 24 by default. A wholly future or wholly empty year is cheap — no month in it can hold a settled gap, so none is read.
Call this directly — no other tool call is needed first (the workspace is resolved from the caller's authorized token, same as every other well_* tool).
| Name | Required | Description | Default |
|---|---|---|---|
| year | No | One calendar year to return in full — all twelve of its months, December back to January, instead of the recent window. Use it to reach a year the recent window does not cover, in either direction; future months come back visible but never selectable. Accepted range: 2000-2100, also reported as `navigable_years`. | |
| reply | No | One sentence, IN THE LANGUAGE THE USER IS WRITING IN, that the click sends into the conversation as their own message. Write what the PERSON would say about the months they pick, in their words, not an instruction to yourself. You write it BEFORE they click, so you cannot know what they will choose: put {picked} where the pick belongs and the card replaces it with the names they actually picked, one or several. In English it would read "Let's work on {picked}." Write the same shape in the user's language. {picked} is required: a sentence that names a pick itself names the one you guessed, so the card refuses it and sends its own English instead. At most 160 characters. Omitted sends an English sentence the card builds itself, which is wrong for any reader not writing in English. | |
| title | No | Heading for the picker card shown to the user. At most 120 characters. | |
| months | No | How many recent calendar months to return, ending with the current month. Each month holding activity costs one extra read for its invoice state. Ignored when `year` is given. | |
| purpose | No | Why the months are being listed, set by the calling skill's own flow — never inferred from this message. "close" is book closure: the card paints close readiness, and each month carries its per-day `close_days` and the counts behind its "why not ready" errands. "analysis" is reporting: the card offers only the months a canvas can report on, paints the categorization day axis, counts on the execution date the canvas aggregates measure on, and therefore omits the books-dated errand counts entirely rather than reporting them as 0 — set it ONLY when a reporting skill is already running its own period-pick step, not just because this message names one (a burn figure, a cost breakdown, "my report"). Naming a report is phrasing; it never sets this field on its own. Omit or "collect" for invoice retrieval (the default, and the right choice for a plain "what months do you have" question, even one that mentions why) — this paints the retrieval axis and skips the close-readiness fields. | |
| subtitle | No | Supporting line under the picker card's heading. At most 240 characters. | |
| workspace_id | No | Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| hints | No | |
| periods | Yes | |
| success | Yes | |
| next_step | No | What to do with the card this result renders. Added by the dispatcher when the card asks for a click. |
| workspace_id | Yes | |
| base_currency | Yes | |
| default_period | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| navigable_years | Yes | The calendar years a `year` request is answered for. A picker's year steppers stop here. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
| fiscal_year_start_month | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and destructiveHint=false, and the description adds substantial behavioral context beyond that: the meaning of null vs absent values, the difference between transaction_count and bank_transaction_count, the caveat that `days` is empty for analysis calls, and the cost model of extra reads per month. It even explains future-month behavior, default_period fallback, and which fields are absent under `purpose: "analysis"`.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well sectioned with PURPOSE, WINDOW, and COST headings and is front-loaded with the core action, but it is extremely long and contains redundancy. For example, invoice_state and missing_invoice_count are introduced in the main bullet list and then revisited in a later bullet, and each day-axis variant (days, analysis_days, close_days) gets a lengthy exposition. It is structured, but it is not concise; many sentences could be trimmed without losing essential guidance.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with 8 optional parameters, an output schema, and a rich domain, this description is comprehensively complete. It covers the picker's fields, the semantic traps (absent vs 0, selection vs analysis vs retrieval), the purpose-dependent behavior, the window options, cost implications, and even the necessary conversation/workspace context. Nothing needed for safe and correct invocation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 goes far beyond the schema. It explains when to use `year`, how `months` influences cost, how `purpose` should be set by the calling skill's flow and never inferred from user phrasing, the `reply` language requirement, and the `conversation_id` continuity rule. This adds real decision-making value for the agent.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'List the recent accounting months of the workspace, with each month's close status, its invoice-retrieval state, and the counts that describe how much work it holds.' It also clearly distinguishes this listing tool from the many sibling list tools by focusing on the period picker and its field-level semantics.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-to-use guidance: call this before any close, review, or month-scoped read; ask the user which month to work on; never guess or derive a month from today's date. It also explains when to pass `purpose: "close"`, `purpose: "analysis"`, or omit it for invoice retrieval, including the explicit note that a user merely mentioning a report is not the trigger for the analysis purpose.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_list_recurring_contextsList the billing contexts a reader can count as recurringARead-onlyInspect
List the billing contexts a reader can count as recurring revenue over one window, each with what counting it would add. This is what the recurring-contexts card offers; it measures nothing well_sum_invoices did not already measure.
Each entry in groups is one billing context's revenue in the window: context_key (the id a selection is matched on), label (the context as the product writes it), amounts (one entry per currency, each already net of credit notes and never converted) and count (the invoices behind it). The named contexts are sorted biggest first in the currency that carries the most invoices, so the biggest decision reads first. A context whose window nets to nothing in every currency is NOT listed — counting it would add nothing, so it is not a choice. A context that nets NEGATIVE stays on the list: its credit notes outweighed its invoices, which is a real state, and hiding it would move the figure by an amount nobody saw.
The last entry may be context_key: "unclassified", labelled "No billing context". It is the invoices whose billing_context is null: extraction fills the field rather than a billing system, so on most workspaces it holds most of the revenue. It is a choice like the others. A business that bills only subscriptions can count it as recurring; a business with one-off work usually cannot. When the reader counts it, apply it to the well_sum_invoices rows whose billing_context is null — no row carries the key itself. State its amount whenever it is listed, counted or not, because it is the part of the figure extraction could not describe.
totals is the sum of the groups' amounts per currency: the window's whole readable issued revenue. This read converts nothing and never adds one currency to another. The reader decides per context, so the choice needs no single total; convert once, in the arithmetic, at a rate you state.
This read takes no view on which contexts ARE recurring, and offers no default, the "No billing context" entry included. What counts as recurring revenue is a fact about the reader's business, not about the vocabulary: a retainer is recurring for one company and a one-off engagement for another.
Take the reader's answer from the card: its Continue records it with well_switch_workspace as recurring_contexts, and you read it back with well_wait_for_selection (kind "recurring_contexts"). Keep only the well_sum_invoices rows whose billing_context is in that answer, reading unclassified as the rows whose billing_context is null, and pass the same keys to well_render_mrr as recurring_contexts so the figure names what it counted.
The window is whole months: from and to are both the first day of a month, YYYY-MM-01, from inclusive and to EXCLUSIVE. window echoes both back exactly as you sent them. When a comparison will be measured, read the list over BOTH windows, from the start of the earlier one to the end of this one, so a context that stopped billing between them is still offered.
partial: true means the aggregate was cut short: every amount here is a FLOOR, a context's real share can only be larger, and a choice made because a share looked small may not survive the full read. Say so before presenting the list as a basis for the decision. unreadable_rows counts invoices whose net amount or currency could not be read at all; they are in no figure here. It is null when that count could not be read, which is not zero: say it is unmeasured.
Read success before groups: a failed read returns no contexts, which looks exactly like a window with nothing to choose.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| to | Yes | EXCLUSIVE end of the window: the first day of the month after the last one you want, YYYY-MM-01. | |
| from | Yes | Inclusive start of the window: the first day of a month, YYYY-MM-01. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| groups | Yes | |
| totals | Yes | |
| window | Yes | |
| partial | Yes | True when the aggregate behind these figures was cut short. Every amount is then a FLOOR rather than a measurement. |
| success | Yes | |
| next_step | No | What to do with the card this result renders. Added by the dispatcher when the card asks for a click. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| unreadable_rows | Yes | Invoices in the window whose net amount or currency could not be read. They are in no figure here, including the no-billing-context one. Null when the count could not be read. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark this as read-only and non-destructive, so the description's job is to add behavioral context. It adds a great deal: no currency conversion, no built-in default for what counts as recurring, negative contexts stay listed, zero contexts are omitted, `partial: true` means amounts are floors, `unreadable_rows` can be null-but-not-zero, and `success` must be read before `groups`. None of this is 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long, but the tool has nuanced output semantics and decision-support caveats that justify the length. It is front-loaded with purpose, uses bolded key concepts, and organizes content into coherent blocks: entries, totals, no-default stance, workflow, window rules, and failure semantics. A few sentences are somewhat expansive, but most earn their place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite having an output schema, the description explains the full shape of `groups`, `totals`, `unclassified`, sorting, partial reads, unreadable rows, and failure behavior. It also covers multi-workspace authentication and the exact follow-up workflow with sibling tools. Nothing an agent needs to call and interpret this tool correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 reinforces `from` inclusive / `to` exclusive and workspace-auth behavior, but those are already fully described in the schema. It adds a little output-behavior context (`window` echoes back, `partial` floors amounts), but not enough new parameter-level meaning to push above the baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb+resource: 'List the billing contexts a reader can count as recurring revenue over one window, each with what counting it would add.' It also distinguishes itself from a sibling by noting it 'measures nothing `well_sum_invoices` did not already measure,' so an agent can tell this is the choice-presentation read rather than the revenue-summing read.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-to-call guidance: 'When the token authorizes one workspace, call this directly' and 'When it authorizes several... pass workspace_id on the call.' It also explains when to read over both windows for comparisons, and describes how the result feeds into `well_switch_workspace`, `well_wait_for_selection`, and `well_render_mrr`. This is far beyond implied usage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_list_retargetable_connectorsList retargetable connectorsARead-onlyInspect
Read the connectors on this workspace's lineage parent (its membership workspace) that could follow it here, WITHOUT showing the user anything: each candidate's connector, how strongly it was proved to belong to this company, and how much transaction history is behind it. This draws nothing on the user's screen and asks for no confirmation.
Use it ONLY for a silent CHECK the model acts on itself: the close-books bank step deciding whether a candidate exists on the parent before it offers the retarget card, a step that needs the candidate count. An empty list is the normal answer for a workspace connected correctly the first time. Read the count and act in the same turn — there is no card and no click to wait on.
⚠️ To have the USER bring a connector across, call well_show_retargetable_connectors INSTEAD — that one draws the card the user confirms. This tool cannot draw one, so a retarget step run here leaves the user with nothing to act on.
| Name | Required | Description | Default |
|---|---|---|---|
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| next_step | No | What to do with the card this result renders. Added by the dispatcher when the card asks for a click. |
| candidates | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and destructiveHint=false, so the safety profile is covered. The description adds valuable behavioral context beyond annotations: it draws nothing on the screen, asks for no confirmation, and is intended as a silent check that returns a count. This enriches the agent's understanding of side effects without contradicting 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is longer than average but is well structured: the core purpose is front-loaded, followed by a clear usage rule and a distinct warning block with an emoji. Every sentence adds value, and the contrast with the sibling tool is prominent. It could be tightened slightly, but it is not bloated.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's specific use case (silent check, no UI, return count), the description fully covers what an agent needs: what the tool returns (candidates, proof strength, history), when an empty list is normal, and the instruction to act in the same turn. The presence of an output schema further reduces the need to explain return values. Nothing essential is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both workspace_id and conversation_id are already documented in the input schema. The description does not add any new parameter semantics beyond what the schema provides; it only mentions reading the count, which is output behavior. Thus the baseline 3 is appropriate since the schema carries the semantic load.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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 connectors on this workspace's lineage parent... that could follow it here' and explicitly contrasts with the sibling well_show_retargetable_connectors, which draws a card. It clearly distinguishes the read-only, silent list operation from the user-facing show variant, 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-to-use guidance: 'Use it ONLY for a silent CHECK the model acts on itself' and names the exact scenario (close-books bank step). It also states when not to use it, pointing to well_show_retargetable_connectors as the alternative for user-facing actions, and notes that an empty list is normal. This is textbook usage guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_list_uncategorized_windowList uncategorized transactions in a windowARead-onlyInspect
List the transactions in a date window that carry no category, so a figure that depends on categorization can say exactly what is missing before it is computed.
from is inclusive and to is EXCLUSIVE — for whole months, pass the first day of the month after the last one you want.
These rows are measured on when the movement happened (executed_at), not on its accounting date. That is deliberate and it matters: the two disagree about which MONTH a transaction belongs to for a large share of real data, and many rows carry no accounting date at all. A caller listing rows on one basis while summing a figure on the other ends up with rows it counts but cannot offer to fix. Pair this with a sum measured on the same basis.
Returns each row's identity, amount, counterparty and the classifier's pending suggestion where one exists. It lists rows with NO category; a categorized row that has not yet posted to the ledger is a booking question and is not returned here.
meta.truncated: true means the page filled and more rows exist, so report the count as a floor rather than as the total. meta.returned is what came back.
success: false means the window is UNKNOWN, not empty. The read failed, so no count exists and returned and truncated are absent rather than zero. An empty records on a failed read is not "nothing is uncategorized" — treating it that way reports a clean list this read never produced. Say the list could not be read.
Do not propose categories from this list. Where the classifier has a proposal it rides on the row, and the assignment surface is where a category is chosen.
When the user asks to see, list or fix these rows, call this tool directly: its card is the answer. ⚠️ This tool draws its card on EVERY call, the empty one included. So when nobody asked for the list and you only need to CHECK whether anything is left (the first pass of a gate, or a re-check after a repair), call well_get_worklist_status({ worklist: "uncategorized_window", from, to }) first, with the same scope you would pass here. It draws nothing. Call this tool after it only when it answers open: true.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| to | Yes | EXCLUSIVE end of the window, YYYY-MM-DD. | |
| from | Yes | Inclusive start of the window, YYYY-MM-DD. | |
| limit | No | Max rows to return (default 500). | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| window | Yes | |
| records | Yes | |
| success | Yes | |
| returned | No | |
| truncated | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses behavior far beyond the readOnlyHint/destructiveHint annotations: it explains the card-drawing on every call, the executed_at vs accounting date basis, the semantics of meta.truncated and success:false, and the workspace_id guessing behavior. This is rich, non-obvious behavioral context that annotations alone cannot convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is longer than average but well-structured with bolded key semantics and paragraph breaks. It front-loads the core purpose and then adds necessary caveats. Every sentence carries substantive content; nothing is fluff. The length is justified by the tool's subtle behaviors, though it could be trimmed slightly without losing meaning.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the output schema existence, the description doesn't need to detail return fields, but it goes further to explain meta.truncated and success:false behaviors, which are critical for correct interpretation. It covers failure modes, workspace handling, and the card side-effect. Nothing an agent needs to call this correctly and interpret results is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 meaningful nuance to parameters: it clarifies that 'from' is inclusive and 'to' is exclusive with a month-boundary example, explains when to omit or pass workspace_id based on token scope, and details the conversation_id requirement for continuity. This goes beyond the schema descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a clear verb+resource: 'List the transactions in a date window that carry no category'. It further distinguishes this from related operations by noting it returns only rows with NO category and excludes categorized-but-unposted rows, which is a different booking question. This differentiates it from siblings like well_list_unposted_transactions.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly gives when-to-use and when-not-to-use guidance. It directs callers to well_get_worklist_status when only checking status (to avoid drawing a card), and tells when to call this tool directly. It also warns against proposing categories from this list and clarifies measurement basis pairing with a sum. Alternatives are named and conditions are explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_list_unposted_journalsList journals still to postARead-onlyInspect
List the transactions AND invoices of a fiscal period whose journal entry a posting retry can still clear on its own — the re-triggerable posting gap. An empty list with scan_truncated: false means no re-triggerable row remains in the rows scanned. That is NOT proof the period is posted: this read omits rows halted on a substantive reason a repost cannot clear (a missing ledger account, a tax gate, a locked period), so never declare the period posted on an empty read alone. If the close still reports an unposted blocker for this period, those rows need an accounting decision, not a re-trigger.
This is NOT the categorization surface. For a row that still needs a category or a ledger account, use well_list_unposted_transactions. This read carries only the rows that are ready to post and simply have not yet: the posting pipeline did not run. It never lists a row halted on a substantive reason (a missing ledger account, a tax gate, a locked period) — a retry only re-fails those, and the categorization and hydration steps own them.
Each row carries source_id, source_kind (transaction or invoice), name (the counterparty composite), amount, and period_date.
in_flight_processing: true means Well is still processing these rows — enrichment (classification, matching, re-extraction) is in flight, so wait and re-read rather than reposting. Re-read on the close's wait cadence until it is false; only then is the (i)-set settled enough to re-trigger. The paired write is well_repost_journals.
scan_truncated: true means the (i)-set was read from a bounded slice of the workspace's rows, not all of them. An empty list under this flag means "no re-triggerable row was found in the rows scanned", NOT "every row is posted" — do not tell the user the close can skip this step on a truncated empty read.
The period is named in FISCAL terms, and a workspace's fiscal calendar need not follow the calendar year. Take fiscal_year and fiscal_period from a well_list_periods entry, or from the months the user already selected this session; never derive them from a calendar month yourself.
success: false means the period is UNKNOWN, not clear. The read failed, so no count exists, and an empty records on a failed read is not "everything posted".
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max rows to return. | |
| fiscal_year | Yes | The fiscal year of the period to read. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| fiscal_period | Yes | The fiscal period, 1-12 for a month; 13 is the adjustment period and resolves to no rows. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| records | Yes | |
| success | Yes | |
| returned | No | |
| scan_truncated | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| in_flight_processing | Yes | |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint and destructiveHint annotations, the description discloses critical behaviors: an empty read is not proof the period is posted, scan_truncated means only a bounded slice was read, success:false means the period is unknown, and rows halted on substantive reasons are intentionally omitted. These nuances materially change how an agent interprets results.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is lengthy but front-loaded with the core purpose and the most dangerous misinterpretation, then uses bolded flags and short paragraphs to structure caveats. There is some repetition of the 'not proof posted' warning, but the redundancy reinforces a high-risk semantic and does not obscure the message.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity, the description covers purpose, failure semantics, flags, sibling routing, workspace authorization behavior, paired write, and interpretation of empty results. The existence of an output schema and complete input-schema descriptions further reduce the burden, and 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.
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 guidance beyond the schema: fiscal_year and fiscal_period must come from well_list_periods or session selections, never derived from calendar months, and workspace_id behavior is tied to token authorization. This exceeds the schema's parameter descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a precise verb and resource: it lists transactions AND invoices of a fiscal period whose posting gaps a retry can clear. It distinguishes itself from the sibling categorization surface, well_list_unposted_transactions, so an agent can tell exactly which tool to pick.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage guidance is explicit: it names the alternative tool for categorization needs, identifies well_repost_journals as the paired write, instructs the agent to re-read on the close's wait cadence when in_flight_processing is true, and explains when to pass workspace_id based on token scope. This is direct, actionable routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_list_unposted_transactionsList transactions still to postARead-onlyInspect
List the transactions of a fiscal period that carry a category or a role and have STILL not reached the ledger, so a close can say exactly what is holding it.
This is the posting gap, not the categorization gap. A row here already has a category; what it lacks is the ledger account its journal entry would post to. For rows carrying no category at all, use well_list_uncategorized_window.
Each row carries transaction_id, label, amount, period_date, the current_ledger already attached where one is, and ledger_suggestions — the classifier's proposals, each with the account's code (its number, e.g. "6156") beside its name.
The ledger_catalog.accounts list carries every account this workspace can post to, with the id well_set_transaction_ledger_account takes. A row whose ledger_suggestions is empty is assigned from that list: the classifier proposed nothing, which is not the same as the row having nowhere to go.
The rows arrive snake_cased (period_date, ledger_suggestions, current_ledger), unlike well_list_uncategorized_window, whose close cousin emits camelCase. A caller reading one shape against the other silently sees empty fields rather than an error.
The period is named in FISCAL terms, and a workspace's fiscal calendar need not follow the calendar year: "June 2026" is not reliably fiscal period 6. Take fiscal_year and fiscal_period from a well_list_periods entry, or from the months the user already selected in this conversation; never derive them from a calendar month yourself.
Most categories already determine their ledger account: the chart maps each category key to a canonical code, and only a handful abstain because the category alone cannot pick a safe account without the transaction direction. So a long list here usually means the categories are missing, not the accounts.
success: false means the period is UNKNOWN, not clear. The read failed, so no count exists, and an empty records on a failed read is not "everything posted".
truncated: true means the period holds MORE unposted rows than this page carries, so returned is a floor rather than the period's total. Narrow the period, or state the count as "at least".
When the user asks to see, list or fix these rows, call this tool directly: its card is the answer. ⚠️ This tool draws its card on EVERY call, the empty one included. So when nobody asked for the list and you only need to CHECK whether anything is left (the first pass of a gate, or a re-check after a repair), call well_get_worklist_status({ worklist: "unposted_transactions", fiscal_year, fiscal_period }) first, with the same scope you would pass here. It draws nothing. Call this tool after it only when it answers open: true.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max rows to return. | |
| fiscal_year | Yes | The fiscal year of the period to read. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| fiscal_period | Yes | The fiscal period, 1-12 for a month; 13 is the adjustment period and resolves to no rows. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| records | Yes | |
| success | Yes | |
| returned | No | |
| truncated | No | |
| ledger_catalog | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and destructiveHint=false, and the description adds substantial behavioral context beyond that: snake_case vs. camelCase field casing, fiscal-period semantics, success:false meaning unknown rather than clear, truncated:true meaning the returned count is a floor, and the fact that the tool draws its card on every call.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but tightly packed: every paragraph introduces a distinct, decision-relevant caveat. It front-loads the core purpose, then organizes critical operational behaviors with clear framing, so the length is justified rather than padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with this many edge cases — failed reads, truncation, sibling-tool routing, workspace authorization, fiscal-period ambiguity, and output casing — the description covers every behavior an agent needs to call it correctly. The presence of an output schema and full parameter descriptions further completes the picture.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 schema already documents each parameter. The description adds real value on top by explaining where fiscal_year and fiscal_period should come from (well_list_periods or conversation context, never calendar math), when workspace_id is required, and which parameter feeds well_set_transaction_ledger_account.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb and resource: list transactions of a fiscal period that carry a category or role and have not reached the ledger. It also explicitly differentiates itself from well_list_uncategorized_window, so an agent can tell the posting gap apart from the categorization gap.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit when-to-use guidance: call directly when the user asks to see, list, or fix these rows; call well_get_worklist_status first when only checking whether anything is left. It also spells out workspace_id handling for single vs. multi-workspace token authorization and states not to derive fiscal periods from calendar months.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_list_workspacesList workspacesARead-onlyInspect
List the workspaces this connection is authorized to access. This draws nothing on the user's screen.
Use this FIRST when a single token may cover more than one workspace, and use it for every case a caller can settle on its OWN: exactly one workspace, a hint that matches one, a pin this conversation already wrote, or none at all. Read the rows and say which workspace you took.
⚠️ TO ASK THE USER WHICH WORKSPACE, CALL well_show_workspace_picker INSTEAD. It draws one tile per workspace and waits for a click. Reach for it only when the token authorizes several AND no hint resolves — a chooser over a set of one asks nothing, and a chooser the caller could have answered itself asks a question it already knows the answer to.
Use this FIRST when a single token may cover more than one workspace. Each entry has:
workspace_id: pass this as the workspace_id argument on other tools to target one workspace.
workspace_name: human-readable name (null if it can't be resolved).
is_primary: true for the token's default workspace (used when you omit workspace_id on a write).
own_company_id: the public id of the company this workspace is anchored to, or null. A row that carries it is a company workspace: the close flow runs in one. A row without it is a membership workspace, the container a sign-up mints.
lineage_parent_workspace_id: the workspace_id of the membership this workspace was created under, or null when the workspace has no active lineage. A membership workspace (no own_company_id) whose id appears here on other rows is the parent of those company workspaces.
identity: the company behind the workspace (registered name, trade name, registry number, country, website, currency, fiscal year start, where the fiscal year start came from, and the jurisdiction's default fiscal year start), so two similarly-named workspaces can be told apart. Every field is null when the workspace has no accounting settings yet. Tax identifiers are deliberately not included.
has_bank_transactions: whether a connector the workspace BANKS with has delivered any transaction to it, meaning a bank, a neobank, or a treasury or spend platform whose product is an account. An accounting platform and a payment processor deliver transactions too and do NOT count here. Neither does a transaction whose source connector is unknown, whose install has since been disconnected, or whose catalog entry has been retired. Only
trueshows that a bank has fed this workspace:falsemeans no such transaction was found andnullmeans the signal could not be read, so an absent value is never a zero and neither value licenses skipping a bank-connection step. Read this before any month read when the flow needs to know whether the workspace banks with anything at all.
The result also carries session, what the user's card clicks have already recorded in this conversation: pinned_workspace_id (null when not switched), workspace_queue (the workspaces to work through next, empty when none), selected_periods (the months picked on the period card, empty when none), and selected_counterparties (the counterparties picked on the missing-invoices card, with the workspace their company ids belong to; null when none was picked). Call this any time you need to resync with clicks you may have missed.
When the token authorizes a single workspace you can omit workspace_id everywhere; when it authorizes several, read tools fan out across all of them unless you pass a workspace_id, and write tools require one.
⚠️ A row without own_company_id is a membership workspace with no company of its own. TO ASK THE USER WHICH COMPANY that workspace IS — to show its detected company candidates and let them pick — CALL well_show_company_candidates, never this read: this list never shows the candidates.
| Name | Required | Description | Default |
|---|---|---|---|
| workspace_id | No | Target workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| session | No | What this conversation's card clicks recorded so far; null/empty fields when nothing was clicked yet. |
| success | Yes | |
| workspaces | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and destructiveHint=false, but the description adds extensive behavioral context: no screen drawing, the semantics of session fields, how workspace_id omission behaves, fan-out across workspaces, and the meaning of has_bank_transactions including null and false caveats. This far 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but well-structured with a clear opening directive, warnings, and a field-by-field breakdown. It earns most of its length, though 'Use this FIRST when a single token may cover more than one workspace' appears twice and some guidance about asking the user is repeated in two warning blocks.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity, an output schema exists, and the description explains all returned row fields, session state, edge cases such as null values, and the decision logic for calling this tool vs pickers or candidate tools. An agent has everything needed 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.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema already documents both parameters well, so a baseline of 3 is appropriate. The description adds meaningful context beyond the schema: workspace_id is the value to pass to other tools from returned rows, omission behavior is tied to token authorization, and conversation_id must be passed back across the conversation. This elevates it above baseline but some content repeats schema text.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence states a specific verb and resource: 'List the workspaces this connection is authorized to access.' It explicitly notes the tool draws nothing on screen and explicitly distinguishes itself from well_show_workspace_picker and well_show_company_candidates, making sibling differentiation clear.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-to-use guidance: use this FIRST when a token may cover multiple workspaces, and lists the cases a caller can settle on its own. It also gives exact exclusion rules, naming well_show_workspace_picker and well_show_company_candidates as the alternatives with conditions for choosing them.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_preview_invoice_fetchPreview invoice fetchARead-onlyInspect
Preview which vendors a past period is still missing supplier invoices from, where each one's invoices are, and which route would obtain them. Use it for "what would happen if I fetched 's missing invoices?" before anything runs.
Name the period ONE way: { calendar_year, calendar_month } (the calendar month, e.g. June 2026 → 2026, 6), { fiscal_year, fiscal_period }, or periods: [{ calendar_year, calendar_month }, …] for SEVERAL months in one call (1-12), or name NO period at all to use the months the user selected on the period card in this conversation (well_list_periods → the user clicks → well_switch_workspace records them). With no period named and no months selected, the call refuses and tells you to run the period step first. Every month must have ended: a current or future month is refused, and so is the adjustment period (13). Duplicate months are refused.
COST: there is no batch endpoint, so each named month is a separate read of that month's spend. Ask for the months the user actually named, not a whole year "to be safe".
Returns vendors — EVERY vendor of the rows THIS CALL covers, one entry per supplier portal ACROSS the whole window (one portal is one place to go, however many months it spans), or one per counterparty where no portal matched: name, provider_id, domain, url and url_source, the counterparties it covers (each tagged with calendar_year, calendar_month, period_label and suggested_route), tx_count, base_total_amount in base_currency. THE ROUTE NEVER FILTERS vendors: a vendor Well has no published flow and no connector for is listed exactly like the rest, with its route on its counterparties. WHAT the call covers is a separate question, and two fields answer it: a counterparty pick narrows the rows to the picked companies (see scoped_to_selected_counterparties below), and a hints line names any group the projection could produce no vendor for. So vendors is every vendor of the rows THIS CALL covers, which is the whole period only when neither of those is present. upload_rows (the user must supply the file) and connect_rows (connecting the named service fetches it) carry the same counterparties again, split by route, with the same month tags.
WHERE A VENDOR'S INVOICES ARE: url_source says how much url knows. "blueprint" is the page Well's own published flow opens, so it IS the billing page. "enrichment" is the vendor's front door — the catalog entry address or the company's domain — so the user still has to find the invoices on it. "none" means no address at all and url is null. Never present an "enrichment" address as the invoice page. url_source informs and gates nothing: an "enrichment" vendor is offered for the pick, and carried on the link, exactly like a "blueprint" one.
ROUTES DESCRIBE HOW, NOT WHETHER: a counterparty Well holds a connector for is in connect_rows AND under its vendor, where its entry reads suggested_route: "connect" and connect_routed_counterparties counts it. Connecting is the route to suggest; the agent run stays available so the user has a way through when the connector does not work for them. A counterparty on suggested_route: "upload" is in upload_rows too. Never present the same counterparty as two separate gaps — it is one gap seen twice, so count it once.
counts covers the rows this call actually read, and every field states its own unit: vendors and agents count PORTALS, agent_tx counts TRANSACTIONS, upload and connect count COUNTERPARTY ROWS — one counterparty per month. They are not summable with each other: never add them into one total, and vendors is never the sum of the other four, because every counterparty reaches the vendor list whichever route it takes. A total over the whole window counts the DISTINCT counterparties named in vendors, and a counterparty appearing again in upload_rows or connect_rows is the same gap seen by its route. Across several months a counterparty counts once per month in upload and connect, while vendors and agents count each portal once for the window, so neither is the sum of the months' own. WHEN scoped_to_selected_counterparties IS PRESENT, vendors, upload_rows, connect_rows, counts AND months COVER ONLY THE PICKED COUNTERPARTIES, NOT THE WHOLE WINDOW: for the months the pick bounded, every row and every figure here is built from the picked rows alone, and selection_scope says how many counterparty rows it left out. Never report those rows as every vendor the period is missing an invoice from, and never report those counts as the period's own — state the truncation and its size, and point at well_list_missing_invoices for a fresh card that drops the pick. Without that field the counts cover the whole window. months gives each month's own counts; periods_covered names the months. A sum is null when any member of it had no FX rate, never a partial figure. The single-month fields calendar_year, calendar_month, fiscal_year, fiscal_period and period_label appear ONLY when the call named exactly one month.
THIS TOOL LAUNCHES NOTHING. It creates no task, starts no run, and fetches no invoice — mode is always "preview" and nothing_launched is always true. Launching the agents is NOT available on this surface, so present the preview as information and do not promise to run it.
collect_url is the ONE link to hand the user: the /collect page, which asks the Well browser extension to run these portals. It names each portal by its provider_id, and that id is the only field that decides which portal runs — a name or an address in the link labels a row and nothing more. Give the link as returned and never build one or edit its parameters. The page starts nothing until the user acts on it, it reports which portals the extension accepted, and it never reports that an invoice arrived. The link also names this workspace, and that name gates WHO may act on the link: the page starts nothing until the reader is signed in to Well as a member of it. It does NOT choose where the invoices land — the extension files into whichever workspace it is signed in to — so never tell the user the link picks the destination. THE LINK CARRIES EVERY VENDOR THAT HAS AN ADDRESS, whatever its url_source and whether or not Well holds a published flow for it. Deciding what a vendor's invoices need once the page opens belongs to the app and the extension, not to this read, so url_source labels a vendor and never withholds it. Two things still keep a vendor off the link: no address at all, and no provider_id the link can address. collect_url is null when the window holds no addressed vendor at all; collect_url_omits names the vendors a full window pushed past the 25-portal ceiling, and collect_url_unaddressable names the ones the link cannot name. A vendor on either list is still missing its invoice, so say the link cannot carry it, and offer the upload or the connect route from upload_rows and connect_rows instead. Never say it has nothing outstanding.
Only CATEGORIZED expense transactions are considered — uncategorized spend is not counted, so poor categorization coverage under-reports what an agent run would cover; disclose the hints.
Call this directly — no other tool call is needed first (workspace is resolved from the caller's authorized token, same as every other well_* tool).
| Name | Required | Description | Default |
|---|---|---|---|
| periods | No | Several calendar months in one call, 1-12. Each month costs one separate read, so name only the months you need. Duplicates are refused. | |
| fiscal_year | No | Fiscal year (the calendar year the workspace's fiscal year STARTED in). | |
| workspace_id | No | Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did. | |
| calendar_year | No | Calendar year, e.g. 2026. | |
| fiscal_period | No | Fiscal period, 1-12. The adjustment period (13) is refused: it has no calendar month. | |
| calendar_month | No | Calendar month, 1 = January … 12 = December. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| mode | No | |
| error | No | |
| hints | No | |
| counts | No | The whole window's counts. `vendors` and `agents` count the distinct portals across it, so neither is the sum of the months' own. `upload` and `connect` add each month's counterparty rows, so a counterparty missing an invoice in two of the months read counts once per month. |
| months | No | Per-month route counts, oldest first. |
| success | Yes | |
| vendors | Yes | EVERY vendor of the rows THIS CALL covers, whatever route its invoice would arrive by — one entry per supplier portal across the whole window, or per counterparty where no portal matched. The ROUTE never filters this list: a vendor Well has no published flow and no connector for is listed exactly like the rest. What the call covers can still be narrower than the period, and the envelope says so: when `scoped_to_selected_counterparties` is present these are the picked counterparties alone and `selection_scope` sizes the remainder, and a `hints` line names any group the projection could produce no vendor for. |
| next_step | No | What to do with the card this result renders. Added by the dispatcher when the card asks for a click. |
| collect_url | No | The `/collect` entry that hands these vendors to the Well browser extension, naming each one by its `provider_id` and naming this workspace as the link's authorization scope. The page starts nothing until the reader is signed in to Well as a member of it. It carries every vendor that has an address, whatever that address's `url_source`, because what a vendor's invoices need once the page opens is the app's and the extension's decision rather than this read's. Null in three unrelated cases: no vendor of the window carries an address, none of the addressed vendors carries an id the link can address, or this read could not name the workspace the link authorizes. The hints name which one, and only the first is a verdict on the vendors. Opening it starts nothing on its own: the user acts on the page. |
| fiscal_year | No | Present only when the call named exactly one month. |
| upload_rows | Yes | Counterparties whose invoice only a manual upload can obtain. |
| connect_rows | Yes | Counterparties whose invoice arrives by connecting a service. |
| period_label | No | Human-readable label of the period, e.g. "June 2026". Present only when the call named one month. |
| workspace_id | No | |
| base_currency | No | |
| calendar_year | No | Present only when the call named exactly one month. |
| fiscal_period | No | Present only when the call named exactly one month. |
| calendar_month | No | Present only when the call named exactly one month. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| periods_covered | No | The months the result covers, oldest first. |
| selection_scope | No | What the pick removed. Present with `scoped_to_selected_counterparties`, so the size of the truncation is readable beside the result. |
| nothing_launched | No | Always true — this tool never starts anything. |
| collect_url_omits | No | The portals `collect_url` does NOT name, because one link carries at most 25. Present only when the ceiling left some out. Report those vendors as outside the link — it starts nothing for them. |
| periods_requested | No | How many calendar months the call named. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
| collect_url_unaddressable | No | The vendors `collect_url` does not name: the vendor carries no address at all, or it carries one but no catalog id the link can address. A missing published flow is NOT among the reasons, because the link takes an enrichment address exactly like a blueprint one. Present only when the window holds some. They are real gaps and they are listed in `vendors`; report them as vendors the link cannot carry, never as absent. |
| rows_dropped_by_ownership | No | Counterparty rows this preview dropped because the current user owns none of their gaps — a fetch runs as the current user and can only collect the invoices of counterparties they own. Counted apart from `selection_scope.rows_dropped_by_filter` (the pick's own shortfall), and present only when it dropped at least one. The matching `hints` line names it. |
| scoped_to_selected_counterparties | No | Present when a counterparty pick narrowed this preview: for the months the pick was made against, every route below covers only those companies. A month outside the pick is covered in full. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already provide readOnlyHint=true and destructiveHint=false, but the description adds extensive behavioral context well beyond them: it launches nothing, each named month costs a separate read, refusal conditions (current/future months, adjustment period 13, duplicates, no period and no selection), the collect_url gating on workspace membership, the 25-portal ceiling, and the caveat that only categorized expense transactions are counted. It also discloses that 'enrichment' urls must never be presented as the invoice page and that sums are null when any member lacks an FX rate. All of this is consistent with 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is heavily front-loaded with a clear purpose, but it is severely over-long with visible redundancy. The claim that 'vendors is every vendor of the rows THIS CALL covers' is stated repeatedly across the vendors and counts sections, and several ALREADY-STATED points (e.g., 'the route never filters vendors', 'a counterparty is one gap seen twice') are re-explained in the counts paragraph. While the complexity justifies length, many sentences repeat rather than add; the SCREAMING ALL-CAPS headings and restatements make it harder to scan, not easier.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given an output schema exists, the description correctly focuses on presentation semantics an agent cannot infer from schema or annotations: how to read url_source, why counts are not summable, how scoped_to_selected_counterparties truncates every field, how collect_url must be handed over verbatim and what it does/doesn't do, and the categorization-coverage under-reporting caveat. The refusal conditions, the fallback to the period card, and the no-prerequisite note ('Call this directly — no other tool call is needed first') make the description complete for an agent to invoke it safely and interpret results correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 substantial meaning beyond the schema: it explains the three mutually exclusive ways to name a period ({calendar_year, calendar_month}, {fiscal_year, fiscal_period}, or the periods array), that naming multiple months is one call of 1-12, the cost warning ('each named month is a separate read... not a whole year "to be safe"'), and that workspace is resolved from the caller's authorized token. The schema lists parameters but the description explains the selection semantics and constraints between them, which the schema alone does not convey.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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: 'Preview which vendors a past period is still missing supplier invoices from, where each one's invoices are, and which route would obtain them.' It explicitly frames the tool as the pre-run companion to fetching ('Use it for "what would happen if I fetched <month>'s missing invoices?" before anything runs'), and the SIBLING contrast is sharp: 'THIS TOOL LAUNCHES NOTHING' distinguishes it from well_enqueue_invoice_fetch, while the pointer to well_list_missing_invoices separates it from the list-only sibling. An agent can tell exactly what this tool is and is not.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description states when to use it ('before anything runs'), how to name periods, and the fallback behavior when no period is named (use the period card selection or refuse). It also points to well_list_missing_invoices when a fresh, unpicked card is needed. However, it never explicitly names well_enqueue_invoice_fetch as the alternative to use when the user actually wants to launch the fetch — the contrast is implied through 'LAUNCHES NOTHING' and 'Launching the agents is NOT available on this surface' rather than stated as a direct routing rule.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_propose_next_stepsPropose next stepsARead-onlyInspect
Put five next steps on a card, as five lines the person can send.
This tool ranks nothing. The five skills and their order come from well_get_session_digest's suggested_steps; pass them in that order. The sentence beside each one is yours to write, in the language the person is using, from that skill's own quoted utterances and the figures the digest returned. When that list is empty or shorter than five, do NOT call this tool and do not write five of your own: say in one line that the next steps cannot be proposed this time. The server checks that each line can travel and hands the list to the card. Call it at the END of a skill that tells you to, never to work out what the person should do.
Each step is a pair:
skillthe slug of a Well skill, exactly as well_search_skill lists it. A slug the catalog does not hold is refused, and the refusal names the slugs it does.promptone natural sentence, 1 to 160 characters, written from that skill's own quoted trigger utterances. Write what the PERSON would say, in their words, not an instruction to yourself.
REFUSED rather than rendered:
a step naming a brick a flow invokes (
define-workspace,define-period,normalize-currency) or one of the two skills that call this tool (signing-back,whats-next): nobody sends those, so rank another skill in its placea prompt that starts with "/": the host reads it as a command, not as a message
a prompt containing "<": the host can read it as markup
a prompt containing a line break: a row carries one line
fewer or more than 5 steps: the card is a fixed list
Clicking a line records that pick on this connection. Read it back with well_wait_for_selection({ kind: "next_step", timeout_s: 60 }) in this same turn: on "selected", take selection.next_step.prompt as the person's own message and start selection.next_step.skill at once, loading it with well_get_skill. When no turn is waiting, the card sends the sentence into the conversation itself as the person's own message. That is the recovery, not the plan: it arrives as a fresh turn that starts from nothing you already hold. Every row stays clickable while the card is on screen, and a click changes nothing about the row: a person who takes a second step later finds the same five lines.
When the card renders, the five lines are already in front of the person and the card sits where this call sits in the turn: write everything you have to say BEFORE calling. After it, the only thing that follows is the well_wait_for_selection call the result's next_step field spells out. Prose in place of that call ends the turn, and the click then has to restart the work from a new message rather than continue this one.
| Name | Required | Description | Default |
|---|---|---|---|
| steps | Yes | Exactly 5 steps, in the order the card lists them. | |
| workspace_id | No | Target workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| hint | Yes | |
| error | No | |
| steps | Yes | |
| reason | Yes | |
| success | Yes | |
| next_step | No | What to do with the card this result renders. Added by the dispatcher when the card asks for a click. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Even with readOnlyHint=true and destructiveHint=false already in annotations, the description adds substantial behavioral detail: it does not rank, it refuses invalid slugs and malformed prompts, it enforces exactly five steps, it records clicks, it defines the recovery fallback when no turn is waiting, and it mandates that all prose be written before the call. There is no contradiction with 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but front-loaded with the core purpose and the crucial 'ranks nothing' caveat. Each paragraph covers a distinct concern: source and ordering, pair semantics, refusal cases, selection flow, and call sequencing. Some schema-level prompt constraints are repeated, making it slightly verbose, but the length is mostly earned given the number of failure modes the tool must handle.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity, the description is remarkably complete: it specifies when to call, when not to call, how to construct each step, what gets refused, how clicks are handled, how to read the selection with well_wait_for_selection, and how to sequence prose before the call and the follow-up call after it. Since an output schema exists, not documenting return values is acceptable.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although the schema has 100% description coverage, the tool description adds critical parameter semantics: pass the five skills in the digest's order, write prompts in the person's language from the skill's quoted utterances, use the skill slug exactly as well_search_skill lists it, and produce exactly five steps. It also explains the reasoning behind prompt constraints like no leading '/', no '<', and no line breaks — going beyond the bare schema constraints.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a concrete action and outcome: 'Put five next steps on a card, as five lines the person can send.' It immediately distinguishes itself from siblings by stating 'This tool ranks nothing' and by pointing to well_get_session_digest's suggested_steps as the source of the five skills, so an agent can tell exactly when this card-rendering tool applies.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is made explicit: 'Call it at the END of a skill that tells you to, never to work out what the person should do.' It also gives a clear negative condition — when suggested_steps is empty or shorter than five, do NOT call the tool and instead say next steps cannot be proposed. This is specific, actionable guidance that prevents misuse.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_query_recordsQuery recordsARead-onlyInspect
Read records from Well's context graph FOR YOUR OWN WORK. This draws nothing on the user's screen.
Use it for every read whose answer is yours rather than the reader's: a gate checking whether a window holds transactions, a totalCount an answer has to quote, a sync log's latest status, a field a later step needs, the rows behind a figure you are about to compute.
⚠️ TO SHOW THE USER A TABLE, CALL well_show_records INSTEAD. Same arguments, same rows, and it renders the root's own table. This tool cannot put one on screen, so a request to "show me my invoices" answered here leaves the user with prose where a table belongs.
⚠️ WORKFLOW:
Call well_get_schema(root) FIRST to discover the available fields.
Name in
fieldsONLY the extra values you need (5-15 typically). They are ADDED to the root's default projection in the payload you read.Filter with
whereClauseso the read answers the question. A count under a filter beats reading rows and counting them yourself.
ROOTS (read-only — all 33): companies, people, connectors, invoices, documents, transactions, accounts, payment_means, workspace_connectors, memberships, cards, checks, ledger_accounts, journals, journal_entries, tax_rates, exchange_rates, invoice_transactions, categories, account_balances, tasks, workspaces, invoice_payment_means, chat_conversations, blueprint_runs, workspace_connector_sync_logs, media, emails, phones, web_links, locations, invoice_items, billing_events (The accounting graph — ledger_accounts, journals, journal_entries — and balances/rates are read-only projections owned by the sync/posting pipelines; query them for financial context, you cannot create/update them here. Sub-resources like emails/phones/locations are usually richer when read via their parent company/person.)
CATEGORY CATALOGS: "categories" holds two independent taxonomies, separated by category_type. Always filter on it — an unfiltered read mixes them:
whereClause: { category_type: { _eq: "company" } }is the COMPANY-CATEGORY catalog: the industry labels a counterparty carries, and the idswell_update_company({ category_ids })accepts. There is no curated allowlist — the labels are minted during enrichment — so read them here rather than inventing a taxonomy.whereClause: { category_type: { _eq: "transaction" } }is the management/transaction taxonomy.
CONNECTED TOOLS: do NOT use this tool to show the user what they have connected — call well_list_connectors instead. It owns that job: connection status, and an install link for anything not connected yet. Query root "workspace_connectors" here only for genuine RECORD-level needs — reading sync timestamps, filtering connections, joining them with other roots. ("connectors" is the installable catalog; "workspace_connector_sync_logs" is per-sync history.)
Well already syncs the providers' data into the roots above — invoices, transactions, accounts, the accounting graph. ALWAYS read it from here. well_invoke_connector_tool and a provider's own tools are for an ACTION the user explicitly asked to take on that provider (e.g. "create this record in Attio"), never a way to fetch data Well already holds.
FILTERING (whereClause):
Uses Hasura-style operators on field names.
Safe operators (work on ALL field types): _eq, _neq, _in, _nin, _is_null
Numeric/date only: _gt, _gte, _lt, _lte
Text only: _like, _ilike
When unsure of a field's type, prefer _eq or _in (they always work).
Combine with _and, _or, _not
For relationship fields, use nested syntax: { "issuer": { "company_id": { "_eq": "" } } }
NEVER select the workspace's OWN records by matching a company name. One legal entity appears under several labels — a registered name, a trade name, a bank-issued label — so a name filter silently drops rows and the total reads as complete. On the invoices root, pass
partyScopeinstead: it resolves the workspace's own side on the server, so this query needs no id lookup and no extra call. Call well_get_own_company for the id only when a root has nopartyScopeand you must filter on issuer_pk / receiver_pk or the nested company_id yourself.Match a counterparty by id too whenever you have one. Reach for _ilike on a name only to DISCOVER candidates to show the user, never to compute a figure you will report. Examples: { "status": { "_eq": "unpaid" } } { "grand_total": { "_gt": 1000 } } { "local_currency": { "_eq": "EUR" } } { "_and": [{ "status": { "_eq": "unpaid" } }, { "grand_total": { "_gte": 500 } }] } { "issuer": { "company_id": { "_eq": "" } } }
SORTING (orderBy):
Sort by any field: { field: "grand_total", direction: "desc" }
Default sort is by primary key ascending.
⚠️ RULES:
fieldsis ADDITIVE — it widens the data you receive on top of the root's default projectionOmitting fields (default view) or naming a few extras both beat allFields
Field paths from schema: "invoices.issuer.name" → ["invoices", "issuer", "name"]
Default 50 records per request, max 500.
Reading whether ANYTHING matches is one call at
limit: 1: readtotalCount, not the rows.
EXAMPLE - does the window hold any transactions at all? well_query_records({ root: "transactions", limit: 1, whereClause: { "executed_at": { "_gte": "2026-06-01", "_lt": "2026-09-01" } } }) // totalCount answers it. One row comes back and you ignore it.
EXAMPLE - answer "how much is still owed on the unpaid invoices?": well_query_records({ root: "invoices", fields: [["invoices", "balance_due"]], whereClause: { "payment_status": { "_in": ["unpaid", "partial"] } } }) // balance_due arrives in the rows for you to total up.
ONE CALL IS THE ANSWER — do not walk the root:
Every response carries totalCount (ALL matches, not just this page) and records_url (the full web-app table, with your filter and sort already applied). Hand the link to the user for anything past this page.
A non-null
nextCursoris NOT a to-do. It means more rows exist, whichtotalCountalready told you and the link already covers.Never paginate to compute a total, count, average or breakdown: aggregate over the filtered set instead. Summing a paginated sample produces a wrong number.
Never paginate to "be thorough". Large roots will exhaust the output limit mid-walk, and the user ends up with nothing legible.
Paginate ONLY for per-row work over every match that no aggregate can express, and tell the user the cost before starting. Then: pass the returned
nextCursorascursor;nextCursor: nullis the last page.
Returns { rows, totalCount, nextCursor, success }.
| Name | Required | Description | Default |
|---|---|---|---|
| root | Yes | The entity type to query — any of the 33 read-only roots (companies, people, connectors, invoices, documents, transactions, accounts, payment_means, workspace_connectors, memberships, cards, checks, ledger_accounts, journals, journal_entries, tax_rates, exchange_rates, invoice_transactions, categories, account_balances, tasks, workspaces, invoice_payment_means, chat_conversations, blueprint_runs, workspace_connector_sync_logs, media, emails, phones, web_links, locations, invoice_items, billing_events). Call well_get_schema(root) first to discover fields. | |
| limit | No | Max records to return (default 50, max 500) | |
| cursor | No | Opaque cursor for the next page. Omit for the first page, then pass nextCursor from the previous response. | |
| fields | No | EXTRA field paths to add to the root's display view, for values you need to reason about. Each path is an array whose first segment is the root's table name — use the paths well_get_schema(root) returns verbatim, which is the root name for every root except people (whose table is peoples); a path opening with any other segment is dropped. Additive only: they widen the payload you receive, and the root's own display projection (the columns the Well web app shows, and the ones a table drawn from this query carries) stays what it is no matter what you pass here. A scalar a composite renders comes back AS that composite — asking for grand_total gets you composite_total_amount_currency, with grand_total inside it — so read `columns` for what was actually materialized. Omit unless you need a value the display view does not carry. | |
| orderBy | No | Sort results by a field. Example: { field: "grand_total", direction: "desc" } | |
| allFields | No | If true, automatically fetches all scalar fields from schema. No need to specify fields. | |
| partyScope | No | Which side of an invoice the workspace itself occupies, resolved from its own company rather than a party name. `invoices` root only. "purchase" = the workspace owes it (payables); "sales" = the workspace is owed (receivables); "intra_self" = both parties are companies the workspace owns; "unattributed" = Well cannot place it on either side. The four partition every invoice, so report the "unattributed" count beside any payable total rather than dropping it — an unattributed invoice may still be owed. Prefer this over hand-writing an issuer/receiver filter. | |
| whereClause | No | Hasura-style filter object. Operators: _eq, _neq, _gt, _gte, _lt, _lte, _like, _ilike, _in, _nin, _is_null. Example: { "status": { "_eq": "unpaid" } } | |
| workspace_id | No | Target workspace. Omit to query every authorized workspace at once; each row comes back tagged with the workspace it belongs to. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes | Query results |
| error | No | |
| columns | No | The materialized columns in display order, with each composite substituted in place of the source fields it consumed. A row object's key order does not preserve this — the flattener appends reconstructed composites last — so a UI that wants the web app's column order must read it from here. |
| success | Yes | |
| returned | Yes | Number of rows returned |
| columnMeta | No | Per-column field meaning, keyed by the same column paths as the rows. `context` = what the field means; `enrichment` = how the value is sourced (e.g. Bank sync, AI extraction). Only documented columns appear. Read this to interpret the returned values. |
| nextCursor | No | Cursor for the next page. null means last page. |
| totalCount | Yes | Total matching records |
| records_url | No | Login-gated deep link to the FULL web-app records table for this root (real DataTable: composites, inline editing, resize/pin), carrying this call's `whereClause` and `orderBy` so it opens on the same rows. Hand it to the user for everything past this page — it is the answer to 'show me all of them', not pagination. Null when no workspace is in context or no web page serves the root. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and destructiveHint=false, and the description consistently matches them (no contradiction). It adds substantial behavior beyond that: the tool 'draws nothing on the user's screen,' totalCount reflects ALL matches not just the page, nextCursor semantics are explicitly de-emphasized ('NOT a to-do'), composite fields materialize as their parent composite, and invalid field paths are silently dropped. This is exactly the contextual layer annotations cannot express.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long, but it earns its length for the suite's central read tool: purpose and sibling routing are front-loaded, and the body is partitioned by headers (ROOTS, CATEGORY CATALOGS, CONNECTED TOOLS, FILTERING, RULES, ONE CALL IS THE ANSWER). Minor deduction for partial redundancy with the schema — the full 33-root list, partyScope enum semantics, and fields additive behavior appear in both places.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 10-parameter tool with nested filtering over 33 roots and an output schema, the description is remarkably complete: it covers the prerequisite schema discovery step, per-root ownership semantics (read-only accounting projections, parent/child sub-resources), category taxonomy disambiguation, a full filtering reference, pagination ethics with cost disclosure, and two worked examples. Return values are covered by the output schema plus a one-line envelope description, so 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.
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 decision-critical semantics the schema lacks: an operator-type compatibility table (_eq/_in always work; _gt numeric/date only; _like text only), nested relationship syntax, the company-name matching trap and why partyScope resolves it safely, and the 'one call is the answer' rule tying limit:1 to totalCount. For whereClause, fields, and cursor, this description is effectively a usage manual far beyond the schema's per-parameter notes.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence names a specific verb and resource ('Read records from Well's context graph FOR YOUR OWN WORK') and adds the crucial scope qualifier 'This draws nothing on the user's screen.' It explicitly differentiates from siblings: 'TO SHOW THE USER A TABLE, CALL `well_show_records` INSTEAD' and 'call well_list_connectors instead' for connection status, so an agent can disambiguate without opening other schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides an explicit decision rule ('Use it for every read whose answer is yours rather than the reader's') with concrete examples of eligible reads. It names three alternatives with their exact conditions — well_show_records for tables, well_list_connectors for connection status, well_invoke_connector_tool only for user-requested provider actions — and prescribes a workflow (well_get_schema first, additive fields, whereClause filtering).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_remove_contact_channelRemove contact channelADestructiveInspect
Remove a contact channel from a company or person.
Wraps the resource-scoped DELETE endpoints (DELETE /v1/{companies,people}/:id/{emails,phones,web-links,locations}/:channelId).
Pass channel_id = the UUID of the specific channel row to remove (NOT the parent). Find it by reading the parent with well_query_records and selecting the channel's id field.
| Name | Required | Description | Default |
|---|---|---|---|
| parent | Yes | Parent record type: company or person | |
| channel | Yes | Channel to remove: email | phone | web_link | location | |
| parent_id | Yes | UUID of the parent company or person | |
| channel_id | Yes | UUID of the specific channel row to remove | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| parent | No | |
| channel | No | |
| success | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the destructiveHint=true annotation, the description discloses that this wraps DELETE endpoints and warns that channel_id must be the specific channel row, NOT the parent — a meaningful scoping caveat that prevents an agent from accidentally deleting the parent record. It doesn't cover consequences to related data, but the annotation already carries the destructive profile.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, zero filler. Purpose is front-loaded, then endpoint context, then the critical usage instruction. Every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Complete for a 7-param tool with an output schema and destructiveHint annotation. The main pitfall (confusing channel_id with parent_id) is addressed, and workspace_id/conversation_id/idempotency_key are fully documented in the schema. Minor gaps like permission requirements are not covered, but they are not glaring given the schema richness.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 genuine value by clarifying the parent/channel relationship — channel_id is the row to remove, not the parent — and by chaining to well_query_records for how to obtain it, which the schema alone does not convey.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Remove a contact channel from a company or person') and clarifies the exact target by distinguishing the channel row from the parent. The 'NOT the parent' note disambiguates it from broader deletion tools like well_delete_company and well_delete_person.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit lookup procedure: find channel_id by reading the parent with well_query_records and selecting the channel's id field. It names the alternative for discovery (well_query_records) yet doesn't explicitly contrast with the sibling well_add_contact_channel, though the verb 'remove' vs 'add' makes the pairing self-evident.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_render_burnRender a caller-computed burnARead-onlyInspect
Put a burn figure YOU computed onto the burn card.
This tool measures nothing. It takes the figure and its method as input and returns them for rendering. Call it only after you have computed the burn yourself and can state every field below from your own work — never to "get" a burn.
The server derives no burn of its own. The figure on the card is the one you state here, which is why every field below is required: the policy behind a number is the only thing that makes it checkable.
Under the number the card draws nothing. It carries the figure, the window it averages and the trend chip; everything else you state below is REQUIRED and reaches no pixel. All of it comes back to you in this tool's text result, which is what you write the prose from. The card is the measure; the explanation is yours.
REQUIRED, because a figure whose method is not stated cannot be checked:
amount— the outflow per month, as a POSITIVE magnitude incurrencywindow— the months the average divides by, not the months that carried spendconvention— "signed", and the counts you elected it frommonths_in_windowandmonths_with_data— a window with dark months reports LOWER than its typical month. When the two differ you MUST say so in prose: how many months recorded an outflow, and that the average still divides by the whole windowexcluded— what fell out, in named groups.internal_transfersis the sum'sexcluded_multi_leg; sendnullwhen the sum could not count ittransaction_countandunplaceable_count— how much of the window could be placed inside or outside the transfer rule at all.unplaceable_countis the sum'sexcluded_no_owned_leg. Sendnullwhen the sum could not count it. Never send 0 for that, because zero says every row was placed
REFUSED rather than rendered:
a negative
amount— a burn is a magnitude; a negative one means a signed subtotal was used without taking its magnitudeconvention: "magnitude"— that feed keeps direction in a field no grouping here reaches, so no outflow was measuredmonths_with_dataabovemonths_in_window, or a measuredunplaceable_countabovetransaction_countone of
unplaceable_countandexcluded.internal_transfersnullwithout the other: one cancelled count nulls botha
months_in_windowthat disagrees with the monthswindowspans — the two state one fact, and a reader cannot tell which is the liesignedelected from ZERO negative rows: whatever the convention was called, that window measured no outflowconvention_countssumming pasttransaction_count, ormonths_with_datadisagreeing with the monthsper_monthshows carrying an outflow — your own prose states both, so a contradiction between them is a sentence that refutes itselfa
windowwhose bounds are not each the first of a month, or that fits inside one month: a month average divides by whole monthsa
per_monthseries that is not the window's own months, in order, averaging toamount— a dark month belongs in it as a zero, and a series that disagrees with the figure is not the working behind ita
currencyoutside ISO-4217 — the code is checked against the catalog, not its shape
OPTIONAL, and only as a pair:
baselineandchange— the earlier window you compared against, its own average, and the signed percentage between them. Send both or neither: a percentage whose baseline the reader cannot name is exactly the unchecked number this tool refuses everywhere else. The card draws the CHIP alone and never the baseline, so sending the pair obliges you to NAME that comparison in prose: the baseline window and its own average. Compute the baseline the same way you computed the figure, over a window of the same length; the two may overlap, and when they do say so too.changeis checked againstamountandbaseline.valueand refused when it does not follow from them, so send the percentage you actually divided. Do not send a direction: down is GOOD for a burn, and the card's green is decided server-side fromchangerather than read off its sign.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| amount | Yes | Average monthly outflow as a POSITIVE magnitude. A negative value is refused. | |
| change | No | Signed percentage against `baseline.value`, drawn as the card's trend chip. Send it only alongside `baseline`, whose window and average your prose must name, and never derive the card's up/down sense from its sign — for a burn, down is good. | |
| window | Yes | Inclusive start and EXCLUSIVE end of the averaged window, YYYY-MM-DD. | |
| baseline | No | The earlier window this figure is compared against, and its own average. Required for `change` to render, and never rendered itself: name it in prose, because a percentage whose baseline the reader cannot find anywhere is a number they cannot check. | |
| currency | Yes | ISO-4217 code the amount is denominated in. Checked against the catalog, not its shape. | |
| excluded | Yes | The three exclusion groups kept apart: structural, reader-chosen, and defective. | |
| per_month | No | The series behind the average. A month with no outflow belongs in it as a zero. | |
| convention | Yes | Which sign the feed uses for an outflow. "magnitude" is refused: it measures no outflow. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| months_in_window | Yes | The divisor — every month in the window. | |
| months_with_data | Yes | How many of those months carried any outflow. | |
| convention_counts | Yes | The row counts the convention was elected from, so a reader can check the election. | |
| transaction_count | Yes | Rows in the window. | |
| unplaceable_count | Yes | Rows with no leg on an owned account, neither inside nor outside the transfer rule: the sum's `excluded_no_owned_leg`. `null` when the sum could not count them, never 0. |
Output Schema
| Name | Required | Description |
|---|---|---|
| trend | No | |
| amount | Yes | |
| change | No | |
| window | Yes | |
| success | Yes | |
| baseline | No | |
| currency | Yes | |
| excluded | Yes | |
| per_month | No | |
| convention | Yes | |
| computed_by | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| months_in_window | Yes | |
| months_with_data | Yes | |
| convention_counts | Yes | |
| transaction_count | Yes | |
| unplaceable_count | Yes | |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnly/destructive annotations, the description discloses server behavior in detail: 'The server derives no burn of its own,' 'the card draws nothing under the number,' 'All of it comes back to you in this tool's text result,' and a long list of refusal conditions. No contradiction with annotations; it substantially enriches what the agent knows.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with bold section headers (REQUIRED, REFUSED, OPTIONAL) and front-loaded purpose, but it is quite long and contains some repetition ('a figure whose method is not stated cannot be checked' appears in spirit multiple times). It could be tightened without losing substance.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with 15 parameters, nested objects, and extensive refusal rules, the description covers required fields, optional pairing, workspace handling, and output behavior. Since an output schema exists, the return format need not be detailed, and nothing essential for correct invocation appears missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 real meaning for many parameters (e.g., window is 'the months the average divides by, not the months that carried spend', unplaceable_count null-pairing rules, baseline/change pairing), but not every property receives extra semantic nuance, so not a 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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 ('Put a burn figure YOU computed onto the burn card') and immediately adds a defining scope: 'This tool measures nothing.' It clearly distinguishes itself from sibling render tools by emphasizing it only renders a caller-computed figure, not one the server derives.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly states when to call ('only after you have computed the burn yourself'), when not to call ('never to "get" a burn'), and even direct-call guidance ('call this directly — no other tool call is needed first'). The workspace authorization nuance is also covered with a concrete instruction.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_render_cash_flow_bridgeRender a caller-computed cash-flow bridgeARead-onlyInspect
Put a cash-flow bridge YOU computed onto the cash-flow waterfall card.
This tool measures nothing. It takes the four terms of a bridge and the gap between them as input, draws the waterfall, and returns them. Call it only after you have read the opening and closing positions and summed the window's flows yourself — never to "get" a bridge.
A bridge rests on one law: the opening, plus the inflows, minus the outflows, lands on the closing. The closing is measured on its own rather than summed from the flows, so the law is a check rather than a given. State the gap as unexplained and the tool verifies the five figures add up; state figures that do not and it refuses.
REQUIRED:
currency— every figure below is in it, each converted before you stated itperiod_start,period_end— the inclusive calendar days the flows coveropening—amount(SIGNED, a workspace can be overdrawn),as_of(the day beforeperiod_start), andderived(true only when you solved it from the law because the reading could not be taken)inflows,outflows— gross magnitudes, both positive; the direction lives in which bar they areunexplained— the SIGNED gapclosing - (opening + inflows - outflows), computed from the figures as you rounded them; zero when they meetclosing—amount(SIGNED) andas_of, the moment the reading was takenreconciles— true when the gap is inside the tolerance below, false when it is past itpartial— true when any term is incomplete: an anchor some accounts had no reading for, flows with rows no owned account could be placed against, rows that could not be read, or a currency with no rate. A cut-short read returns no rows and stops the run before this call
The tolerance is the product's own: 1% of the closing position's size, never less than 1 in the base currency. A bridge that does not reconcile draws an Unexplained bar between the outflows and the closing; one that does draws none.
REFUSED rather than rendered, each because your own figures disagree:
five figures that do not add up to within a cent
reconciles: truewith a gap past the tolerance, orfalsewith one inside ita negative
inflowsoroutflows; each is a magnitude, so a negative one was re-signedan opening not dated the day before
period_starta window ending more than a day from the day the closing was read
a window that starts after it ends, or a date that names no real day
a derived opening with any gap, on a partial read, or over a window with no flows
a closing
as_ofin the future
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| closing | Yes | ||
| inflows | Yes | ||
| opening | Yes | ||
| partial | Yes | ||
| currency | Yes | ||
| outflows | Yes | ||
| period_end | Yes | ||
| reconciles | Yes | ||
| unexplained | Yes | ||
| period_start | Yes | ||
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| steps | Yes | |
| closing | Yes | |
| inflows | Yes | |
| opening | Yes | |
| partial | Yes | |
| success | Yes | |
| currency | Yes | |
| outflows | Yes | |
| period_end | Yes | |
| reconciles | Yes | |
| computed_by | Yes | |
| unexplained | Yes | |
| period_start | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint, the description discloses arithmetic verification, the 1% tolerance floor, refusal conditions, partial-read behavior, and the Unexplained-bar rendering. This is rich behavioral context an agent needs to predict side effects and failures.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but front-loaded: first sentence states purpose, second warns what the tool does not do, then organized REQUIRED/tolerance/REFUSED sections carry the details. Given the 12-parameter complexity, the density is justified with little redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers invocation timing, parameter constraints, validation rules, workspace authorization behavior, and refusal outcomes. Since an output schema exists, not detailing return values is acceptable; an agent has what it needs to invoke correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With only 17% schema coverage, the description compensates by explaining every required parameter: signed amounts, as_of conventions, gross positive inflows/outflows, the unexplained formula, derived-opening restrictions, and partial semantics. It adds meaning far beyond the schema's enum/type declarations.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb ('Put ... onto the cash-flow waterfall card') and resource, and immediately clarifies the tool's non-measuring role. It is clearly distinguishable from siblings like well_render_cash_position and well_render_cash_forecast.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly states when to call ('only after you have read the opening and closing positions and summed the window's flows yourself') and when not to ('never to "get" a bridge'). It also gives workspace-token conditions for calling directly and when workspace_id is required.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_render_cash_forecastRender a caller-computed cash forecastARead-onlyInspect
Put a cash forecast YOU computed onto the forecast card.
This tool measures nothing. It takes the settled month-end series, the anchor, the burn and the projection you computed, and returns them for rendering. Call it only after you have computed both halves yourself: the month-end totals under your cash scope, and the burn under your stated policy. Never call it to "get" a forecast.
The projection is WORST CASE: no revenue arrives, and cash declines by the burn each month until it reaches zero, where it stops. Take the anchor and the burn each to the cent, then each point is max(0, anchor − k × burn) for the k-th month after the anchor. The tool re-derives every point from the anchor and burn you state here, in cents.
The card draws the series, the anchor clause and the worst-case caveat. The cash scope, the burn policy and partial are REQUIRED and reach no pixel. All of it comes back in this tool's text result, which is what you write the prose from.
REQUIRED:
currency, andas_of: the full ISO time of the balances read the series came fromactuals: one{ month, amount }per month, oldest first, ending on the last month that has ended atas_of(UTC). A month no account covered isnull, never 0, and it stays in the list.anchor:{ month, amount, basis }.closed_month_endis the latest settled month-end inactuals.current_positionis today's cash when no month has a settled total. It sits on the grid at the last actual month.burn: the POSITIVE monthly magnitude, its currency,trailing_months, and thewindowit averaged (frominclusive andtoexclusive, eachYYYY-MM-01). The window ends with the last actual month.months_forward(at most 12), andprojection: one{ month, amount }per projected month. When the anchor sits before the last actual month, the months between are projected too, somonths_forwardmust reach past them.cash_scope: the counted account types, whether unknown ownership was counted,anchor_missing_accounts(counted accounts with no reading at a closed-month anchor; 0 undercurrent_position), and the four exclusion groupsburn_policy: the elected convention and its counts, the exclusions (internal_transfersis the sum'sexcluded_multi_leg,unreadable_rowsits malformed rows), andunplaceable_count(the sum'sexcluded_no_owned_leg)partial: the forecast's own floor, which is WIDER than a cash total'sis_floor. It is checked againstcash_scopeand must betrueexactly when an account was left out with no readable balance, no rate, OR no reading at the anchor month — that last one is the forecast's alone, and a caller that forwards its cash total'sis_floorunchanged is refused on it. It never means a cut-short read: a cut-short balances read or sum stops the run before this call.
REFUSED rather than rendered, each because your own figures disagree:
a projection point that is not
max(0, anchor − k × burn)within a centa projection that does not start the month after the anchor, skips a month, continues after a zero, or has the wrong length
a projection ending on a month that has already ended. The refusal names which of the three causes fired: the cash ran out (report that), the horizon was too narrow for the gap (widen it), or the gap exceeds every legal horizon (the feed is too far behind to project across)
a cash currency that differs from the burn's
a negative burn, a burn elected "magnitude",
signedelected from no negative rows, or one ofunplaceable_countandinternal_transfersnull without the othera
closed_month_endanchor that is not the latest settled actual, or whose amount differs from ita
current_positionanchor beside a settled actual, off the last actual month, or with an account missing at itactual months out of order, repeated, skipped, or ending on any month but the last one that has ended at
as_ofa burn window that disagrees with
trailing_months, or ends on a different month than the actualsa
partialthat disagrees with the floor your owncash_scopeimpliesan
as_ofin the future
This tool renders its own chart card. Do not re-plot the series with a charting tool.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| burn | Yes | ||
| as_of | Yes | ||
| anchor | Yes | ||
| actuals | Yes | ||
| partial | Yes | ||
| currency | Yes | ||
| cash_scope | Yes | ||
| projection | Yes | ||
| burn_policy | Yes | ||
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| months_forward | Yes | ||
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| burn | Yes | |
| as_of | Yes | |
| anchor | Yes | |
| entries | Yes | |
| partial | Yes | |
| success | Yes | |
| currency | Yes | |
| cash_scope | Yes | |
| burn_policy | Yes | |
| computed_by | Yes | |
| months_forward | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and destructiveHint=false, but the description adds substantial behavioral detail: it re-derives projection points, refuses rather than renders on disagreement, renders its own chart card, and instructs the caller not to re-plot. It also clarifies what the card does and does not display, going far 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long, but it is densely packed with essential information and uses clear structural sections (REQUIRED, REFUSED). It is front-loaded with the core purpose and 'measures nothing' warning. Some redundancy exists around not using it to 'get' a forecast, but given the 12-parameter complexity, nearly every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a highly complex tool with 12 parameters, deep nesting, and only 17% schema description coverage, the description is exceptionally complete. It covers required inputs, validation rules, refusal causes, workspace handling, rendering behavior, and output expectations. Any gaps are covered by the existing output schema and parameter-level schema descriptions.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is only 17%, but the description compensates thoroughly. It explains every parameter group: actuals null-vs-zero semantics, anchor basis distinction, burn window inclusivity, projection formula, cash_scope requirements, burn_policy mappings, and the refined meaning of partial. This far exceeds what the sparse schema descriptions provide.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb+resource: 'Put a cash forecast YOU computed onto the forecast card.' It immediately distinguishes the tool from retrieval tools with 'This tool measures nothing' and 'Never call it to "get" a forecast.' This removes ambiguity and clearly separates it from the sibling rendering tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly states when to call it ('only after you have computed both halves yourself'), when not to call it ('Never call it to get a forecast'), and provides important invocation context such as workspace authorization and whether to pass workspace_id. 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.
well_render_cash_positionRender a caller-computed cash positionARead-onlyInspect
Put a cash position YOU computed onto the cash card.
This tool measures nothing. It takes the figure and its method as input and returns them for rendering. Call it only after you have totalled the balances yourself and can state every field below from your own work — never to "get" a cash position.
The server derives no cash figure of its own here, which is why every field below is required: the policy behind a number is the only thing that makes it checkable.
Under the number the card draws nothing. It carries the total and the moment it was read. Every other field below, required or optional, reaches no pixel. All of it comes back in this tool's text result, which is what you write the prose from. The card is the measure; the explanation is yours.
REQUIRED, because a figure whose method is not stated cannot be checked:
amountandcurrency— the consolidated total. NEGATIVE is legal: an overdrawn workspace has negative cash, and this tool renders it rather than refusing it.as_of— the moment the reading is valid foraccounts— every account that CONTRIBUTED, each with its native amount and currency, the converted amount, and the rate applied (nullwhen it was already incurrency). Carryinstitution_nameandmasked_account_numberthrough from the balances read as well: your breakdown names each account by its bank, its name and its masked suffix, or by its currency when the bank and the name are bothnull, and never by its id.scope— the account types you counted as cash, and whether you counted an account whose ownership is unsettledexcluded— what fell out, in four named groups: not owned, out-of-scope type, no readable balance, no FX rate. One merged count hides the difference between a rule the reader chose and a defect in the data.partial— whether the total may be a floor (the skills'is_floor), because an account with no readable balance or no rate was left out of it. The result carries the value derived fromexcluded, whatever you state. It never means a cut-short read: that stops before this call.
REFUSED rather than rendered, each because the caller's own figures disagree with each other:
a total that is not the sum of the contributions listed — totalling a different set than you disclose publishes a figure nobody can audit
a converted amount that does not follow from its native amount and stated rate
an account already in
currencythat carries a rate other than one, or whose converted amount differs from its native onean account in another currency that states no rate
the same account contributing twice
a non-zero total with no contributing accounts
an
as_ofin the futurea
scope.account_typesnaming nothinga
balance_historythat repeats a month or runs out of ordera currency outside ISO-4217 — checked against the catalog, not its shape
OPTIONAL:
balance_history— trailing complete month ends, oldest first. Anullamount is a month no stored row covered; send it as a gap rather than dropping it or sending a zero, and never interpolate between two real points.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| as_of | Yes | ||
| scope | Yes | ||
| amount | Yes | ||
| partial | Yes | ||
| accounts | Yes | ||
| currency | Yes | ||
| excluded | Yes | ||
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| balance_history | No | ||
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| as_of | Yes | |
| scope | Yes | |
| amount | Yes | |
| partial | Yes | |
| success | Yes | |
| accounts | Yes | |
| currency | Yes | |
| excluded | Yes | |
| computed_by | Yes | |
| balance_history | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond annotations (readOnlyHint true), the description reveals significant behavioral traits: it measures nothing, derives no figure, the card draws nothing beyond total and timestamp, and all other fields return in the text result. It also enumerates refusal conditions, clarifies that 'partial' means a floor derived from excluded, and states it never indicates a cut-short read. This is far beyond the basic 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but well-structured with bold headings (REQUIRED, REFUSED, OPTIONAL) and front-loads the core purpose and the 'measures nothing' caveat. Some redundancy exists (e.g., the 'card draws nothing' point is made twice), but the length is justified by the tool's complexity and the need to enumerate validation rules.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (10 parameters, nested objects, numerous validation conditions), the description covers all essential semantic and usage aspects: parameter meanings, refusal triggers, partial semantics, workspace handling, and conversation_id usage. The presence of an output schema means return-value documentation is not needed, and the description is complete for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is only 20%, so the description carries the full burden of parameter explanation. It thoroughly explains each parameter: negative amounts are legal, accounts must carry institution_name and masked suffix with naming rules, excluded groups are named and merged counts lose meaning, partial reflects is_floor, balance_history nulls as gaps, workspace_id selection. This fully compensates for the sparse schema descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific action ('Put a cash position YOU computed onto the cash card') and explicitly clarifies what it does not do ('This tool measures nothing', 'never to get a cash position'). This distinguishes it from measurement or listing tools and from sibling render_* tools by focusing on caller-computed cash rendering.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly states when to call ('only after you have totalled the balances yourself') and when not to call ('never to get a cash position'). It also gives workspace-handling instructions (call directly for single-workspace tokens, pass workspace_id otherwise). It does not name alternative sibling tools explicitly, but the boundary is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_render_cost_structureRender a caller-computed cost structureARead-onlyInspect
Put a cost breakdown YOU computed onto the cost-structure card.
This tool measures nothing. It takes the slices and the method behind them as input and returns them for rendering. Call it only after you have computed the breakdown yourself and can state every field below from your own work, never to "get" a cost structure.
The server derives no breakdown of its own. The chart draws the slices you state here, which is why every field below is required: the policy behind a grouping is the only thing that makes it checkable.
The card draws the ring, the legend and the month. Everything else you state below is REQUIRED and reaches no pixel. All of it comes back to you in this tool's text result, which is what you write the prose from. The chart is the measure; the explanation is yours.
REQUIRED, because a breakdown whose method is not stated cannot be checked:
entries: the slices, largest first, each a POSITIVE magnitude incurrency. Send NO share: this tool derives every share from the amounts and returns them, and an entry carryingpctis refused as an unknown field. At most 4 named slices plus one rolled-upOther, because the card performs no rollup of its ownperiod_startandperiod_end: the INCLUSIVE bounds of the single calendar month covered. Never a quarter, never a span, never a month still runningrung: which grouping produced these categories. State it in prose too, so the reader knows whether they are looking at their own ledger's categories or Well'slabel_provenance: whether a person owns those labels. A chart of accounts synced from an accounting tool ismachine, notcurated: the names came from the provider, not from anyone at the companycoverage: the outflow rows the elected grouping could label, against every outflow row the month held. This is the evidence the rung was elected on, and your prose states itconventionandconvention_counts: which sign means money leaving, and the row counts you elected it fromexcluded: what fell out, in four named groups.no_asset_movementis where CARD SPEND lands, because the transfer rule drops a row with no owned asset leg and a card charge moves a liability. It containsno_owned_leg, so never add them. Send an unmeasured LEG count asnullrather than0, because zero says the rule removed nothing, and one cancelled leg count nulls all three.unreadable_rowsis always measured and takes a number
REFUSED rather than rendered:
an entry carrying
pct, or any other field this schema does not name. The shares are DERIVED here from the amounts, so a share you send is a second opinion the card has no way to reconcileentries out of descending-amount order, more than 4 named slices, or an
Otherslice that is not lasta negative
amount: a breakdown is made of magnitudesa
period_start/period_endpair that is not exactly one whole calendar month, or that names a month which has not endedcategory_keyon any rung butcategory_key, or on the rolled-upOtherslice, which is many categories and is therefore not one of themany
label_provenancebutunlabelledon a rung that carries no category:curated,machineandmixedeach claim that someone or something chose labels the chart never shows. The converse is NOT refused, because a rung elects over the month's rows while the provenance describes the ones that survived into the slices, so a labelled rung whose labelled rows all dropped is legitimatelyunlabelledrung: "uncategorised"sent beside named category slices, which is a breakdown claiming to be the absence of oneconvention: "magnitude": that feed keeps direction in a field no grouping reaches, so no outflow was measured.signedelected from ZERO negative rows is the same finding, demonstrated rather than declaredcoverage wider than the month it covers, or a labelled rung that could label no rows at all
one of
excluded.internal_transfers,excluded.no_owned_legandexcluded.no_asset_movementnullwhile the others are measured: one cancelled count nulls all three, and the refusal is filed againstexcluded.no_asset_movementa
currencyoutside ISO-4217: the code is checked against the catalog, not its shape
An EMPTY entries array is accepted, and it means nothing is categorized for that month. Say that, rather than reporting zero spend: a month with no outflow at all is a different answer and the card says so differently.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| rung | Yes | Which grouping produced these categories. | |
| entries | Yes | The slices, largest first, with the rolled-up `Other` last when there is one. | |
| coverage | Yes | The evidence the rung was elected on, so a reader can check the election rather than take it. | |
| currency | Yes | ISO-4217 code every amount is denominated in. Checked against the catalog, not its shape. | |
| excluded | Yes | The four exclusion groups kept apart: the transfers the rule removed, the rows with no owned asset leg (card spend), the subset of those attributable to no account at all, and the rows dropped as unreadable. Merging them hides the difference between a rule and a defect. | |
| convention | Yes | Which sign the feed uses for an outflow. "magnitude" is refused: it measures no outflow. | |
| period_end | Yes | INCLUSIVE last day of that same month. | |
| period_start | Yes | INCLUSIVE first day of the month covered. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| label_provenance | Yes | Whether a person set or confirmed the labels the reader can see. | |
| convention_counts | Yes | The row counts the convention was elected from. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rung | Yes | |
| entries | Yes | |
| success | Yes | |
| coverage | Yes | |
| currency | Yes | |
| excluded | Yes | |
| convention | Yes | |
| period_end | Yes | |
| computed_by | Yes | |
| period_start | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| label_provenance | Yes | |
| convention_counts | Yes | |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, but the description adds substantial behavioral context: the server derives no breakdown, shares are derived from amounts and any sent pct is refused, empty entries mean 'nothing is categorized,' and the tool returns text for the agent's prose. These details go far beyond what annotations convey and do 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long, but the complexity of a 12-parameter rendering tool with many refusal rules justifies it. It is well-structured with bolded lead sentences, grouped REQUIRED fields, a REFUSED list, and a dedicated empty-entries paragraph. The core purpose is front-loaded in the first sentence, and every section adds needed guidance.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers when to call, what the caller must compute, every parameter's semantic role, refusal conditions, workspace behavior, and what the text result is for. The output schema exists, so the description need not repeat return fields; instead it focuses on the reasoning and constraints an agent needs to invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although schema description coverage is 100%, the description adds critical semantic meaning: why pct is refused, that no_asset_movement contains no_owned_leg and must not be summed together, that null means 'could not count' while 0 means 'rule removed nothing,' and that currency is validated against a catalog. This transforms the schema from a type contract into an operational specification.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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: 'Put a cost breakdown YOU computed onto the cost-structure card.' It immediately distinguishes itself from measurement tools by stating 'This tool measures nothing,' and clarifies that it is for rendering a caller-computed breakdown. This makes the tool's role unambiguous among many well_render_* siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says when to call it ('only after you have computed the breakdown yourself'), when not to call it ('never to "get" a cost structure'), and gives operational guidance about workspace_id for single- vs multi-workspace tokens. It also explains why the rung must be stated in prose, giving the agent decision criteria beyond mere function naming.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_render_mrrRender a caller-computed recurring revenue figureARead-onlyInspect
Put an MRR figure YOU computed onto the MRR card.
This tool measures nothing. It takes the figure and its method as input and returns them for rendering. Call it only after you have computed the recurring revenue yourself and can state every field below from your own work — never to "get" an MRR.
The server derives no MRR of its own. The figure on the card is the one you state here, which is why every field below is required: the policy behind a number is the only thing that makes it checkable.
Under the number the card draws nothing. It carries the figure, the window it averages and the trend chip; everything else you state below is REQUIRED and reaches no pixel. All of it comes back in this tool's text result, which is what you write the prose from. The card is the measure; the explanation is yours.
REQUIRED, because a figure whose method is not stated cannot be checked:
amount— the recurring revenue per month incurrency, net of tax and of credit notes, as the sum returned itwindow— the months the average divides by, not the months that carried revenuemonths_in_windowandmonths_with_revenue— a window with dark months reports LOWER than its typical month. When the two differ you MUST say so in prose: how many months recorded recurring revenue, and that the average still divides by the whole windowrecurring_contexts— the billing contexts the reader confirmed as recurring, as the keys the card recorded."unclassified"among them means the reader counted the invoices with no billing context (the sum rows whosebilling_contextisnull) as recurring. This is the reader's half of the policy and the card cannot show it, so an answer that does not name them leaves the figure uncheckedinvoice_count,unattributed_countandunclassified_count— how much of the window the figure could reach at all.invoice_countis the issued invoices;unattributed_countis the SEPARATE set Well could place on neither side, reported beside it rather than inside it, and it may be larger. An unattributed invoice may still be recurring revenue. An unclassified one carries no billing context: it is in the figure only whenrecurring_contextsholds"unclassified", and when it is, say in prose that the reader chose to count it. Sendnullfor a count the sum returned asnull: it is unmeasured, not zeroexcluded— what fell out, as three invoice COUNTS kept apart:one_off(issued invoices under a context the reader did not count),credit_notes(thecredit_note_countnetted into the figure) andunreadable_rows(the sum'sexcluded_malformed,nullwhen unmeasured)
REFUSED rather than rendered, each because your own figures disagree with each other:
a negative
amount— the credit notes outweighed the recurring invoices, so net recurring revenue fell below zero. That is a finding to report in prose, not a figure to put on a card, and never one to flip to its magnitudean empty
recurring_contexts— an MRR with nothing counted as recurring is not an MRR of zero, it is a policy nobody stated. Say the figure has nothing to measure insteadmonths_with_revenueabovemonths_in_window, orunclassified_countaboveinvoice_counta
months_in_windowthat disagrees with the monthswindowspans — the two state one facta window whose bounds are not month starts — a month-average divides by whole months
changewith nobaseline, abaseline.valueof zero, a baseline that does not start before the window or spans a different number of months, or achangewhose magnitude or sign its own two figures contradicta
per_monthseries that does not name each month of the window once in order, does not average toamount, or disagrees withmonths_with_revenue
Send baseline and change as a pair or send neither. Do not send a direction: the card's colour is decided server-side from the figures. Revenue is higher-is-better, which is the opposite of the burn card and exactly why a caller does not get to state it.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| amount | Yes | Recurring revenue per month, net of tax and credit notes. A negative net is refused, never flipped. | |
| change | No | Signed percentage against `baseline.value`, drawn as the card's trend chip. Send it only alongside `baseline`. | |
| window | Yes | Inclusive start and EXCLUSIVE end of the averaged window, YYYY-MM-DD. | |
| baseline | No | The earlier window this figure is compared against. Required for `change` to render, and never rendered itself: name it in prose. | |
| currency | Yes | ISO-4217 code the amount is denominated in. | |
| excluded | Yes | The three exclusion groups kept apart: the reader's choice, the structural netting, and the defective rows. | |
| per_month | No | The series behind the average. A month with no recurring revenue belongs in it as a zero. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| invoice_count | Yes | Issued invoices in the window. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| months_in_window | Yes | The divisor — every month in the window. | |
| recurring_contexts | Yes | The billing contexts the reader confirmed as recurring, as recorded keys; "unclassified" stands for the invoices with no billing context. At least one: an MRR counting nothing as recurring is a policy nobody stated, not a zero. | |
| unattributed_count | Yes | Invoices Well could place on neither side: a set separate from `invoice_count`, which may be larger. Null when unmeasured. | |
| unclassified_count | Yes | Invoices carrying no billing context. In the figure only when `recurring_contexts` holds "unclassified". | |
| months_with_revenue | Yes | How many of those months carried any recurring revenue. |
Output Schema
| Name | Required | Description |
|---|---|---|
| trend | No | |
| amount | Yes | |
| change | No | |
| window | Yes | |
| success | Yes | |
| baseline | No | |
| currency | Yes | |
| excluded | Yes | |
| per_month | No | |
| computed_by | Yes | |
| invoice_count | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| months_in_window | Yes | |
| recurring_contexts | Yes | |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| unattributed_count | Yes | |
| unclassified_count | Yes | |
| months_with_revenue | Yes | |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=true and destructiveHint=false, and the description adds substantial behavioral detail: it does not compute MRR, it returns the figure and method for rendering, refusal conditions (negative amount, empty recurring_contexts, etc.), and the fact that only certain fields reach the card while others are only in the text result. 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but well-structured with clear sections (REQUIRED, REFUSED, usage notes). It is front-loaded with the key message, and every sentence adds necessary detail for a 15-parameter tool. Some redundancy with schema descriptions exists, but the overall organization earns a high score, just not a perfect one.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite high complexity (15 params, 10 required, nested objects, output schema), the description covers every operational aspect: what to compute, what to send, refusal conditions, workspace handling, and the output text result. Nothing an agent needed to call correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% with rich descriptions, and the tool description goes further: it explains the meaning of window vs months_with_revenue, the significance of unclassified contexts, the role of unattributed_count as a separate set, and refusal logic. This adds substantial semantic context beyond the schema, making parameter usage checks explicit.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a precise verb and resource: 'Put an MRR figure YOU computed onto the MRR card.' It explicitly distinguishes itself from calculation tools ('This tool measures nothing') and from the burn card (revenue is higher-is-better, opposite of the burn card). This makes its role unambiguous relative to siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It clearly says when to call ('only after you have computed the recurring revenue yourself — never to get an MRR') and provides workspace guidance (call directly for single workspace; pass workspace_id for multiple). It also states when to send baseline/change as a pair, and contrasts with the burn card to help select between tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_render_runwayRender a caller-computed runwayARead-onlyInspect
Put a runway YOU computed onto the runway card.
This tool measures nothing. It takes the figure and the two numbers behind it as input and returns them for rendering. Call it only after you have computed the cash and the burn yourself — never to "get" a runway.
A runway is one division, so this tool checks the one thing that can be checked: that the headline follows from the two figures you state with it. State them and it renders; state a headline they do not produce and it refuses.
Under the number the card draws nothing. It carries the months, the moment, the burn's own window when you send one (a bare month count when you do not) and a health badge. The cash amount, the burn amount and partial are REQUIRED and reach no pixel. All of it comes back in this tool's text result, which is what you write the prose from, the division included. The card is the measure; the explanation is yours.
REQUIRED:
months— months of cash left, capped at 36status— "ok", "capped", "infinite" or "insufficient_data"cash— the dividend, amount and currency. SIGNED: an overdrawn workspace is negative.nullONLY under "insufficient_data", so a half you could not measure is reported rather than invented.avg_burn— the divisor as a POSITIVE magnitude, its currency, and thetrailing_monthsit averagedas_of— the moment the reading is valid forpartial— whether the cash may be a floor (the skills'is_floor), because an account with no readable balance or no rate was left out of it. It never means a cut-short read: a cut-short balances read or sum stops the run before this call.
OPTIONAL, and send it whenever you are sending a burn:
window— the months the burn averaged, inclusive start and EXCLUSIVE end, each on a month start. The card names those months instead of a bare count, so a reader can see which months the figure stands on. Without it the card can only pair the cash moment with the number of months, which reads as a window ending at that moment whatever months you actually averaged. Send NONE whenavg_burnisnull.
The statuses carry two distinctions worth stating, because both are easy to collapse:
Zero months is DATA when the cash is gone. A workspace already underwater has a real runway of zero, status "ok" — not "insufficient_data", which means the inputs could not be measured at all.
Unbounded is not the same as long. "infinite" means the workspace is not burning; "capped" means it burns slowly enough that the figure passes the 36-month window the product reports in.
REFUSED rather than rendered, each because your own figures disagree:
monthsthat is notcash ÷ avg_burn— the division is the figure's whole claima cash currency that differs from the burn's; one division needs one currency
a negative
avg_burn; it is a magnitude, so a negative one means a subtotal was re-signed"infinite" with a non-zero burn, or a zero burn reported as anything else
"capped" whose division lands inside the window, or which reports a number other than 36
a division past the window reported as "ok" instead of "capped"
non-positive cash reported as anything but a real zero
"insufficient_data" carrying a months figure, or a null figure under any other status
an unbounded runway reporting anything but the 36-month sentinel
an
as_ofin the futurea
windowwhose bounds are not each the first of a month, or that spans no montha
windowthat spans a different number of months thanavg_burn.trailing_months— the two state one fact, and a reader cannot tell which is the liea
windowsent with no burn to average — an unmeasured burn averaged no monthsa
windowreaching into a month that has not ended yeta
windowbound naming no calendar month, or an unknown key on the object
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| cash | Yes | ||
| as_of | Yes | ||
| months | Yes | ||
| status | Yes | ||
| window | No | Inclusive start and EXCLUSIVE end of the months the burn averaged, YYYY-MM-DD. | |
| partial | Yes | ||
| avg_burn | Yes | ||
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| cash | Yes | |
| as_of | Yes | |
| months | Yes | |
| status | Yes | |
| window | No | |
| partial | Yes | |
| success | Yes | |
| avg_burn | Yes | |
| computed_by | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description is exceptionally transparent, detailing that the tool measures nothing, checks the division, refuses invalid inputs, and returns a text result for prose. It explains the status distinctions and the refusal conditions comprehensively, adding far beyond the annotations (readOnlyHint, destructiveHint) which already indicate safety. 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is very long (over 600 words) and includes extensive detail, some of which is repeated (e.g., the division check). It is well-structured with bold headers and sections, but it is not concise. The verbosity may be justified by the complexity, but it could be trimmed.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the complexity (9 params, nested objects, output schema), the description is remarkably complete. It covers all required parameters, optional window, status distinctions, refusal conditions, and the return text. It omits conversation_id, but that appears to be a standard cross-tool parameter with schema description, and its absence doesn't hinder use. The output schema exists, so return values are defined.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With schema coverage at 33%, the description compensates richly. It explains each required parameter's semantics: cash is signed, avg_burn is a positive magnitude, months capped at 36, status meanings, partial meaning, and window semantics. It also explains conversation_id implicitly? Not mentioned, but that's a cross-tool standard. The description adds substantial meaning beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states clearly that the tool renders a runway the caller computed, and explicitly says it measures nothing. It distinguishes itself from measurement tools by emphasizing it only renders provided figures. The verb 'render' and resource 'runway card' are specific, and the distinction from siblings like well_render_burn is implicit but strong.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly states when to use: 'Call it only after you have computed the cash and the burn yourself — never to "get" a runway.' It also provides guidance on workspace authorization. However, it does not name specific alternative render tools, relying on the title and context to differentiate, so it's clear but not exhaustive.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_repost_journalsRe-trigger postingAIdempotentInspect
Re-run (repost) the posting for this fiscal period's ready rows — the workspace's re-triggerable posting gap. This is the ACTION that books the rows well_list_unposted_journals lists as ready to post: when the accounting is ready and simply has not posted, calling this re-runs the deterministic posters and clears it. It operates on the ALREADY-RESOLVED workspace and takes no arguments — do not list or switch workspaces first.
This is NOT well_list_workspaces (which only enumerates workspaces and posts nothing), and NOT well_set_transaction_ledger_account (which assigns an account to ONE row that is MISSING one). This tool posts rows that already have everything they need; it never assigns an account or picks a category.
Do NOT call this while in_flight_processing is true — Well is still processing the rows (enrichment in flight); wait and re-read instead. Do NOT call it to fix a row that needs a category, a ledger account, or a party decision — those are substantive halts a retry only re-fails, and well_list_unposted_journals never lists them.
The re-run is workspace-scoped and idempotent: it re-posts the whole re-triggerable set, and the posters skip anything already booked, so a repeated press is safe. It returns enqueued (rows that reached their poster) and skipped (each row that did not, with a reason).
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| skipped | Yes | |
| success | Yes | |
| enqueued | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond annotations (idempotentHint=true, readOnlyHint=false, destructiveHint=false), it reveals behavior: posts the whole re-triggerable set, skips already-booked rows, is safe to re-press, returns enqueued/skipped with reasons, and must not run while in_flight_processing is true. The single 'this read' phrase is loose, but the surrounding ACTION/write language matches readOnlyHint=false, so 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The definition is long but every paragraph earns its place: core action first, exclusions, safety condition, idempotency, return shape, and workspace authorization. It is front-loaded and highly scannable.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, it still supplies operational context: when to wait, what happens to already-booked rows, which sibling to use instead, and how workspace auth affects required arguments. 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.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and each parameter already has a rich description, so the description's main contribution is contextualizing workspace_id (omit for single-workspace tokens, pass when several). That restates schema guidance rather than adding new parameter-level semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
First sentence states a specific action and resource: 'Re-run (repost) the posting for this fiscal period's ready rows.' It goes beyond the title to define the exact gap and explicitly differentiates from siblings like well_list_workspaces and well_set_transaction_ledger_account, so an agent can select it 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-to-use ('when the accounting is ready and simply has not posted') and hard when-not-to-use conditions (in_flight_processing true; rows needing a category, ledger account, or party decision), and names the alternative tools. It also covers workspace authorization, which determines whether workspace_id is required.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_resolve_reconciliation_taskResolve reconciliation taskAInspect
Approve or reject one or more reconciliation review tasks (from well_run_register_diff or the in-app review queue).
approve: confirms the match — the link is flipped to active.
reject: dismisses the match — the candidate does not silently re-surface.
Each task_id resolves independently; a failure on one (already resolved, not found) is returned in errors and does not block the rest of the batch.
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | ||
| task_ids | Yes | The review tasks' task_id values. | |
| selection | No | For an unresolved reconciliation review task: the transaction the reviewer picked to settle the invoice. Applies to exactly one task; required on approve, rejected elsewhere. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| errors | No | |
| success | Yes | |
| resolved | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses the effect of each action ('approve: confirms the match — the link is flipped to active', 'reject: dismisses the match — the candidate does not silently re-surface'), which is beyond the bare annotations. It further explains independent resolution and per-task error handling, giving the agent a clear model of partial failures without needing to experiment.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded: a lead sentence, two bullets for the action effects, and one sentence for batch semantics. No filler; every line contributes to the agent's understanding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (6 params, nested object, output schema), the description covers purpose, action semantics, and batch behavior, while the schema handles parameter details and the output schema tells the agent what to expect back. The combination gives the agent 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.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The description adds meaning to action (approve/reject semantics) and to task_ids (independent resolution, per-item errors) beyond what the schema states. With schema coverage at 83%, the remaining parameters like selection, workspace_id, and conversation_id are already well-documented, so the description need not repeat them.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb phrase — 'Approve or reject one or more reconciliation review tasks' — and anchors the resource to its sources (well_run_register_diff or the in-app review queue). This distinguishes it from related siblings like well_resolve_register_diff_gap by scoping it to review-task resolution rather than gap resolution.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It clearly states the input context — tasks come from well_run_register_diff or the in-app review queue — so an agent knows when this tool applies. However, it does not explicitly name alternatives or give 'when not to use' guidance, leaving some 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.
well_resolve_register_diff_gapResolve register diff gapAInspect
Post a well_run_register_diff gap (one of missing_in_register_ids' review tasks) into QuickBooks as a Purchase or Deposit.
Requires the exact ledger_account_id (a UUID, not a name) for both:
bank_ledger_account_id: the bank/cash account the money moved through (e.g. Checking).
category_ledger_account_id: the expense or income category the gap books against.
Look these up first with well_query_records({ root: "ledger_accounts", filters: [...] }) scoped to the register connector — never guess an id or match an account by substring/fuzzy name.
Fails with an error (not a silent no-op) if gap posting is disabled for this workspace, if either account doesn't belong to this gap's register connector, or if either account no longer resolves in QuickBooks.
| Name | Required | Description | Default |
|---|---|---|---|
| task_id | Yes | The gap review task's task_id. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. | |
| bank_ledger_account_id | Yes | ledger_account_id of the bank/cash account. | |
| category_ledger_account_id | Yes | ledger_account_id of the category account. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| task_id | No | |
| external_id | No | |
| already_posted | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only indicate a non-read-only, non-destructive operation. The description adds meaningful behavioral detail: it fails with an error rather than a silent no-op in three specific scenarios, and it warns against guessing or fuzzy-matching account IDs. This materially helps an agent predict side effects and failure modes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core action, uses compact bullets for the two required account IDs, and gives only necessary warnings. Every sentence earns its place; no filler or repetition of obvious schema details.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a mutating QuickBooks-posting tool, the description covers prerequisites, lookup workflow, and failure behavior, with the output schema presumably documenting returns. The only notable gap is that it never explicitly says how to choose between Purchase and Deposit, though the expense vs. income category hint implies the distinction.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 clarifying that bank_ledger_account_id and category_ledger_account_id must be exact UUIDs, not names, and by tying task_id to missing_in_register_ids review tasks. It doesn't deeply elaborate every parameter, but it enriches the most safety-critical ones.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific action and resource: 'Post a well_run_register_diff gap ... into QuickBooks as a Purchase or Deposit.' It grounds the tool in its source domain (well_run_register_diff, missing_in_register_ids) and clearly separates it from sibling reconciliation/posting tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit usage context: exact ledger UUIDs are required, lookup must be done first via well_query_records, and it enumerates failure conditions. It does not explicitly name alternative 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.
well_retarget_connectorsRetarget connectorsAIdempotentInspect
Retarget (bring across) ledger connectors from this workspace's lineage parent onto this workspace — the write behind the connector-retarget card. For each source connector, a new connector row is created here that borrows the parent's credentials and pulls the item's history in on its own first sync; the transactions are not moved.
REQUIRED: source_workspace_connector_ids — the workspace_connector_id of each candidate to bring across, from well_list_retargetable_connectors or well_show_retargetable_connectors. An id that is not a current candidate here is refused; an id whose connector has already been retargeted is reported back under already_retargeted_workspace_connector_ids rather than erroring, so a repeated Confirm is a safe replay.
Only a workspace owner or admin may retarget a connector, and the acting person must also hold an active membership on the parent workspace whose credentials the borrow consumes. A caller without that role or that membership is refused, not silently ignored.
| Name | Required | Description | Default |
|---|---|---|---|
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. | |
| source_workspace_connector_ids | Yes | The workspace_connector_id of each parent connector to bring across, copied from well_list_retargetable_connectors or well_show_retargetable_connectors. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
| created_workspace_connector_ids | No | The connector rows created on this workspace, one per newly retargeted source. |
| already_retargeted_workspace_connector_ids | No | Source ids that had already been retargeted — an expected replay, not an error. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds substantial behavior beyond the annotations: it creates a new connector row borrowing the parent's credentials, pulls history on first sync, does not move transactions, refuses non-candidate IDs, reports already-retargeted IDs under a specific key, and requires owner/admin plus active parent membership. No contradiction with annotations; the idempotentHint aligns with the safe-replay statement.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is organized into three compact, purposeful paragraphs: what the tool does, required parameter semantics and replay behavior, and permission constraints. Every sentence carries information, with no filler or repetition of structured fields.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, return values need not be described. The description covers the operation's effect, the source of valid candidate IDs, edge-case handling, permissions, and idempotent replay. Nothing needed to invoke the tool correctly appears missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 by explaining what happens to non-candidate IDs versus already-retargeted IDs. This gives agents practical validation semantics not present in the input schema itself.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Retarget/bring across') and resource ('ledger connectors from this workspace's lineage parent onto this workspace'), and identifies itself as the write behind the connector-retarget card. This clearly differentiates it from the sibling listing 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly tells callers to source candidate IDs from well_list_retargetable_connectors or well_show_retargetable_connectors, establishing the intended workflow. It also states authorization prerequisites and the safe-replay behavior on repeated Confirm. It lacks an explicit 'do not use if...' exclusion, but the context is otherwise clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_run_register_diffRun register diffAInspect
Diff a workspace's bank transactions against its accounting-register transactions (e.g. QuickBooks), and persist the result.
Every match — hard evidence (structured reference, IBAN, tax ID) or inference-only (memo/payee reading) — is raised as a review task with the candidate already attached (raised_for_review). Nothing links automatically; resolve with well_resolve_reconciliation_task once a human decides.
Bank transactions with no register counterpart come back as missing_in_register_ids, each also minted as a gap review task (gaps_proposed) — resolve one with well_resolve_register_diff_gap once a human names the two ledger accounts. gaps_already_proposed counts gaps re-surfaced from an earlier run that already have an open, unresolved proposal.
Bank transactions NOT confirmed absent from the register come back as contended_in_register_ids — never minted as a gap. Two cases land here: (1) a plausible match lost to a higher-confidence sibling transaction this run, so the register-side movement is already accounted for by the winner; (2) the matcher couldn't produce a trustworthy answer (an invalid model response or a provider failure), so absence was never confirmed. Re-run the diff later; a genuine gap or duplicate should resolve itself once the winner's review task is handled or the matcher succeeds.
Register entries no bank transaction explains come back as unexplained_in_register_ids.
Returns { enabled: false, ... } with all counts 0 if the workspace's register-diff feature is off.
| Name | Required | Description | Default |
|---|---|---|---|
| since_date | No | Only diff bank transactions on/after this date (YYYY-MM-DD). | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. | |
| bank_workspace_connector_id | Yes | The bank connector's workspace_connector_id (e.g. Plaid). | |
| register_workspace_connector_id | Yes | The accounting connector's workspace_connector_id (e.g. QuickBooks). |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| enabled | No | |
| matched | No | |
| success | Yes | |
| gaps_proposed | No | |
| already_linked | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| raised_for_review | No | |
| missing_in_register | No | |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| contended_in_register | No | |
| gaps_already_proposed | No | |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
| missing_in_register_ids | No | |
| unexplained_in_register | No | |
| contended_in_register_ids | No | |
| unexplained_in_register_ids | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description goes far beyond the sparse annotations by disclosing side effects: it persists results, mints review tasks for matches, gaps, and unexplained register entries, and explicitly explains the contended_in_register_ids cases that are never minted as gaps. This is exactly the behavioral context an agent needs beyond readOnlyHint/openWorldHint/destructiveHint.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but well-structured: a front-loaded one-sentence purpose, followed by clear bullets for each output category and the feature-off case. Each bullet carries meaningful behavioral information, though a bit more brevity would be possible without losing important edge-case detail.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity, the description covers all essential behavior: match handling, gap proposals, contended cases, unexplained register entries, re-run guidance, feature-off return, and pointers to resolution tools. The output schema exists, so not repeating return-value structure is fine; the description is complete enough for correct invocation and follow-up.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema already provides 100% description coverage, including purpose, formats, and workspace_id selection rules. The tool description adds overall behavioral context but does not add per-parameter semantics beyond what the schema gives, 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.
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: it diffs a workspace's bank transactions against accounting-register transactions and persists the result. It also distinguishes the tool from its resolution siblings by explicitly naming well_resolve_reconciliation_task and well_resolve_register_diff_gap, so an agent understands this is the diffing step, not the resolution step.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description says what the tool does and gives clear procedural guidance: matching is raised for review, nothing links automatically, use the named resolution tools once a human decides, and re-run later for contended cases. It does not state an explicit 'when not to use' condition or alternative tool for when the feature is off, but the feature-off return behavior is covered.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_search_company_registrySearch the company registryARead-onlyInspect
Search the public company registries for a company by name, to find the one a workspace IS before you create its company workspace. This draws nothing on the user's screen.
Use it in the zero-company case: a membership workspace has no company attached and no detected candidate, so you search the registry for the user's company. Each hit carries an id — the registry ref — that you pass to well_create_company_candidate as registry_ref to mint a candidate from that hit, then well_create_company_workspace to make it the company workspace.
Pass country when the user names one, to scope the search to that jurisdiction. The result carries degraded: true when a provider was unreachable and the hits are partial. Confirm the exact company with the user before you create anything from a hit; never pick one from a name alone.
| Name | Required | Description | Default |
|---|---|---|---|
| q | Yes | The company name to search for. At least 2 characters. | |
| country | No | Scope the search to this jurisdiction. Omit to search across registries. | |
| workspace_id | No | Target workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| hits | Yes | |
| error | No | |
| success | Yes | |
| degraded | Yes | True when a registry provider was unreachable and the hits are partial; the search still returned what it could. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and destructiveHint=false, so the safety profile is covered. The description adds valuable behavioral context beyond annotations: 'This draws nothing on the user's screen' (no UI side effect), 'degraded: true when a provider was unreachable and the hits are partial' (failure mode disclosure), and the instruction to confirm with the user before creating anything. It doesn't describe pagination or result count limits, but the output schema likely covers the return shape, and the key behavioral traits are disclosed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is four sentences, each earning its place: the first states the core purpose and side-effect-free nature, the second gives the exact usage scenario and downstream flow, the third covers the optional country parameter and degraded mode, and the fourth gives a safety rule. It is front-loaded with the most important information and has zero filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
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 a rich output schema, the description covers everything an agent needs to call it correctly: when to use it, what to do with the results, how to handle partial results, and a safety rule. The output schema handles return values, and the annotations handle the safety profile. The only minor gap is no explicit mention of pagination or result limits, but that is not essential for correct invocation and the description is complete for its purpose.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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. The description adds meaning beyond the schema by explaining the semantic role of the result id ('the registry ref that you pass to well_create_company_candidate as registry_ref'), the purpose of country ('scope the search to that jurisdiction'), and the meaning of degraded results. It also clarifies that workspace_id is optional and that omitting it returns the same answer, which is already in the schema but reinforced. The description doesn't add syntax details, but with 100% coverage, the baseline is 3 and the added workflow context raises it to 4.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Search'), a specific resource ('public company registries'), and a precise purpose ('to find the one a workspace IS before you create its company workspace'). It also distinguishes itself from related tools by naming the downstream flow (well_create_company_candidate, well_create_company_workspace) and the zero-company case, so an agent can tell it apart from siblings like well_search_context or well_list_member_candidates.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says when to use this tool ('in the zero-company case: a membership workspace has no company attached and no detected candidate'), how to use the result (pass the hit's id as registry_ref to well_create_company_candidate), and when to pass the country parameter ('when the user names one'). It also gives a clear exclusion: never pick a company from a name alone; confirm with the user. This is explicit when/when-not guidance with named alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_search_contextSearch contextARead-onlyInspect
Search the workspace's recorded notes and context (meeting notes, tickets, imported documents) for a query. Returns compact snippets — each result's "snippets" is an array of one or more matched passages from that note, never the full note body — follow up with well_get_entity on the returned note id for the full record. Use this for questions about the business, a company, a person, a process, pricing, or a past decision. Do NOT use this for a question well_query_records already answers (amounts, counts, lists, filters).
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| topK | No | ||
| query | Yes | Focused search query over the workspace's recorded notes and context. | |
| category | No | ||
| entityId | No | ||
| entityType | No | ||
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| occurredAfter | No | ||
| occurredBefore | No | ||
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| results | Yes | |
| success | Yes | |
| total_returned | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint and destructiveHint annotations, the description discloses key behavior: results contain compact snippets, never the full note body, and well_get_entity is needed for full records. It also reveals the multi-workspace behavior that 'this read will not guess which one you mean', which annotations alone could 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core action and scope, and each subsequent sentence earns its place: output behavior, follow-up tool, use-case guidance, exclusions, and workspace authorization. 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.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with 9 parameters, 1 required, and an output schema, the description covers the essential context: scope, return format, follow-up action, when to avoid it, and how workspace selection works. Since an output schema exists, the description need not restate return values, and nothing needed to call the tool correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is only 33%, and the description compensates for the key parameters by defining what counts as searchable context and when workspace_id is required. Optional filters like category, entityType, entityId, occurredAfter, and occurredBefore are left to inference from their names and types, so not every parameter receives added semantic clarity.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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: 'Search the workspace's recorded notes and context', and gives concrete examples of what that includes. It also distinguishes itself from siblings by explicitly excluding questions that well_query_records already answers and by pointing to well_get_entity as the follow-up for full notes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit guidance on when to use the tool ('Use this for questions about the business, a company, a person...'), when not to use it ('Do NOT use this for...amounts, counts, lists, filters'), and names the alternative tool well_query_records. It also explains exactly how to handle single-workspace vs multi-workspace token authorization.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_search_skillSearch skillARead-onlyInspect
Find the Well procedure for what the user wants to do, when no Well skill is installed in this conversation.
Returns the roster of Well skills with their descriptions; pick the one whose description matches the request, then call well_get_skill with its id and follow the returned instructions exactly.
Call this FIRST for any request that asks to DO a finance job with Well — fetch or chase missing invoices, connect a bank or a tool, pick a period, categorize suppliers.
Route on the form of the request: a job to carry out ("go chase", "get them collected", "connect", "categorize before I close") comes here, while a question about the state of the data ("what is", "which", "how many", "show me", "preview") goes to the matching well_* read tool.
Never call it for a question about the user's data (a cash figure, a runway, a list of records, a period's status): those go straight to the matching well_* read tool. Do not call it when a Well skill is already loaded in the conversation.
| Name | Required | Description | Default |
|---|---|---|---|
| query | No | What the user wants to do, in their own terms. The whole roster is returned either way. | |
| workspace_id | No | Target workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| reason | Yes | |
| skills | Yes | |
| success | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and destructiveHint=false; the description adds behavior beyond that, e.g. the roster is returned regardless of the query, the conversation_id governs the conversation independently of host statements, and the correct protocol after a match is to call well_get_skill. It stops short of saying what to do when no skill matches, so a perfect score is not warranted.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core search/dispatch behavior and is dense with routing rules. It is somewhat long and contains some repetition between the routing sentence and the 'never call it' sentence, so it is not maximally concise.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a routing/discovery tool with zero required parameters and an output schema, the description covers the trigger conditions, exclusions, alternative tools, and the follow-up call (well_get_skill). An agent has everything it needs to decide whether to invoke this tool and what to do with the result.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema entries are already rich, so the heavy lifting is done there. The description reinforces the meaning of query (the user's task wording used to pick a matching skill) and clarifies the workflow context around the parameters, which is a small but real increment over the schema alone.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb and resource: it 'returns the roster of Well skills with their descriptions' and says to follow with well_get_skill. It also states the operating condition 'when no Well skill is installed', which separates it from the many well_* read and mutation siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives an explicit when-to-call rule ('Call this FIRST for any request that asks to DO a finance job') and an explicit when-not-to-call rule ('Never call it for a question about the user's data... Do not call it when a Well skill is already loaded'). It also names the alternative class: matching well_* read tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_set_counterparty_default_categoryDeclare a counterparty's default transaction categoryAInspect
Declare what spend at ONE counterparty is — asked once about the counterparty, instead of once per transaction.
REQUIRED: company_id, from well_list_counterparties. category — a LABEL from the closed list this schema carries. The vocabulary is fixed: there is no free-text category and no way to mint one.
This is the counterparty's DEFAULT, not one row's category. Every transaction of this counterparty categorized from here on takes the label without a model call.
It also reaches backward. The counterparty's existing transactions are relabelled too, in the background over the minutes or hours after the call. Rows a person answered are never touched: a transaction someone categorized or confirmed by hand keeps what they gave it. Tell the user a declaration rewrites the counterparty's history, so they are not surprised by it. To change ONE row instead, use well_set_transaction_category, which sets that transaction and leaves the counterparty alone.
A declaration is trusted at once. The other way a counterparty gets a default is by being taught: three corrections to the same category on three distinct transactions. A declaration skips that, because the person has already said what the answer is.
It overrides whatever the counterparty carried before, including a category the system had inferred from corrections and one an earlier declaration already wrote onto these same rows. A later transaction-level correction still wins over the declaration on the row it names, and teaches the counterparty that the default is wrong.
Do not declare a default for a counterparty whose spend has more than one nature. A marketplace or a cloud vendor selling hardware, compute and advertising to the same buyer has no single answer, and a declaration would state one. Leave those to the classifier and correct them per line.
Not for the workspace's own company. A default is about the other party; the server refuses it on the own company.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| category | Yes | The category label spend at this counterparty defaults to. Must be one of the labels in this list. | |
| company_id | Yes | The counterparty to declare a default for, from well_list_counterparties. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| category | No | The label the counterparty's default now carries. |
| company_id | No | |
| company_name | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Even though annotations already mark this as a mutating write, the description adds substantial behavioral context: the declaration applies to future transactions, relabels existing ones in the background, never touches manually answered rows, overrides previously inferred or declared defaults, and later corrections win. It aligns with annotations and gives an agent the side effects needed to warn a user safely.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but warranted: it uses bold headers and short paragraphs to organize purpose, requirements, forward and backward effects, override rules, exclusions, and multi-workspace handling. It is front-loaded with the core meaning, and each section delivers information an agent must know before calling the tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a mutating tool with side effects and several caveats, the description covers everything needed to call it correctly and inform the user: default semantics, backward relabeling, manual-row protection, precedence rules, mixed-nature warning, own-company refusal, and workspace_id handling. An output schema exists, so the description need not explain return values.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% with strong per-parameter descriptions, so the baseline is 3. The description adds extra meaning by stating company_id comes from well_list_counterparties, that category is a fixed closed-list label with no free-text alternative, and exactly when workspace_id is required versus omitted. It adds less beyond the schema for conversation_id and idempotency_key, so 4 rather than 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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: 'Declare what spend at ONE counterparty is — asked once about the counterparty, instead of once per transaction.' It immediately clarifies this sets a default, not a per-row category, and later explicitly contrasts itself with well_set_transaction_category. An agent can distinguish this tool from its closest sibling 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit when-to-use guidance (per counterparty, not per transaction), names the alternative for single-row changes, and states clear exclusions: do not use for mixed-nature counterparties, do not use for the workspace's own company, and pass workspace_id when multiple workspaces are authorized. This is far beyond implied usage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_set_own_companySet own companyADestructiveInspect
Set which company the workspace itself IS — the confirmed own-company anchor.
REQUIRED: company_id — a company that ALREADY EXISTS in this workspace. Obtain it with well_query_records (companies) or well_create_company; this tool never creates one.
This is a deliberate, accounting-critical write, not a convenience. Anchoring the own company overwrites the workspace's legal identity on its accounting settings (including clearing fields when the anchor moves), records a manual-confirm audit row, and syncs the billing customer name. It never re-posts existing journal entries. Confirm the exact company with the user before calling; never guess one from a name.
Only a workspace owner or admin may set the own company. A caller without that role is refused, not silently ignored.
well_start_close hard-gates on this anchor: a workspace with no own company cannot start a close.
| Name | Required | Description | Default |
|---|---|---|---|
| company_id | Yes | The UUID of a company already in this workspace to anchor as its own company. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| own_company_id | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Despite destructiveHint already being true, the description adds substantial behavioral detail: overwriting the workspace's legal identity on accounting settings, clearing fields when the anchor moves, recording a manual-confirm audit row, syncing the billing customer name, and explicitly not re-posting journal entries. This goes well 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but every section earns its place given the high-risk, accounting-critical nature of the operation. It is front-loaded with the core purpose, then uses clear REQUIRED and warning sections for prerequisites, side effects, authorization, and dependencies. No filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers prerequisites, behavioral side effects, authorization requirements, failure mode for unauthorized callers, and downstream dependencies. An output schema exists, so the absence of return-value details is acceptable. An agent has everything needed to decide whether and how to call this tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 for company_id by stating it must reference an already-existing company, how to obtain it, and that this tool never creates one. That extra semantic guidance justifies a 4.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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 which company the workspace itself IS — the confirmed own-company anchor.' It clearly distinguishes itself from siblings like well_create_company, well_update_company, and well_delete_company by emphasizing that this tool anchors an existing company rather than creating or editing one.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit usage requirements: the company must already exist, must be obtained via well_query_records or well_create_company, and must be confirmed with the user. It also states who may call it (owner/admin), what happens for unauthorized callers, and that well_start_close hard-gates on this anchor.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_set_transaction_categorySet a transaction's categoryAInspect
Set ONE transaction's category — the write that clears a categorization gate.
REQUIRED: transaction_id, from well_list_uncategorized_window. category — the LABEL, exactly as that read returned it on the row's suggestion, or another label from the closed list this schema carries. The vocabulary is fixed: there is no free-text category and no way to mint one.
decision records HOW the category was chosen, and it changes what the row keeps.
accepted_classifier_suggestion— the user affirmed the label the classifier had already put on the row. The row keepscategory_source: "classifier"and its confidence score, and the affirmation is stamped ascategory_confirmed_at. Send this ONLY when the label equals the classifier's own stored suggestion.user_choice— the user picked the label themselves. The row recordscategory_source: "user"with no score.
The server verifies an accepted_classifier_suggestion claim against the row it is writing and downgrades it to user_choice when the stored suggestion is not that label, so the claim can never manufacture classifier provenance. Omitting decision is a user_choice.
A row from well_list_uncategorized_window never qualifies for the affirmation. That read returns rows carrying NO category at all, so there is no stored classifier value to affirm and the claim would be downgraded every time. Its categorySuggestions are PENDING proposals, not a stored category. Clearing that gate is always a user_choice; the affirmation exists for a surface that lists rows the classifier already categorized.
Categorizing a row does NOT move it in or out of the internal-transfer rule — that rule counts payment-means legs and no label affects it. What a category DOES change is exemption matching: an uncategorized row can never be matched by an exemption and always stays in a sum.
One transaction per call. The rows are decided independently and each one is saved as the user decides it.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| category | Yes | The category label to store. Must be one of the labels in this list. | |
| decision | No | How the user arrived at the label. Omit for a user choice. See the description before sending accepted_classifier_suggestion. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| transaction_id | Yes | The transaction to categorize, from well_list_uncategorized_window. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| category | No | The label stored on the row after the write. |
| transaction_id | No | |
| category_source | No | Provenance the server settled on: "classifier" when it accepted the affirmation claim, "user" when the caller picked the label or the claim was downgraded. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| category_confidence | No | The classifier's score, preserved only on an accepted affirmation. Null on a user choice. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already indicate readOnlyHint=false and destructiveHint=false, but the description adds substantial behavioral depth: the server verifies and downgrades accepted_classifier_suggestion claims, omitting decision defaults to user_choice, categorization does not affect the internal-transfer rule, and it changes exemption matching. It also clarifies workspace-scoping behavior and idempotency, all beyond what the annotations express. 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but well-structured: it front-loads the core purpose, uses bolded sections for decision semantics and key restrictions, and every sentence carries useful information. The length is justified by the tool's complexity, though a few phrases could be tightened (e.g., the final workspace guidance uses 'this read' for a write tool, a minor wording slip).
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity and the presence of an output schema, the description is complete: it covers where transaction_id comes from, the closed vocabulary and no-free-text rule, decision verification and downgrade behavior, the interaction with the internal-transfer rule and exemption matching, workspace authorization, idempotency, and conversation continuation. No critical behavior needed to invoke the tool correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, but the description adds rich parameter-level meaning: transaction_id must come from well_list_uncategorized_window, category must be the exact label as returned, decision semantics are fully explained (including when the claim gets downgraded), workspace_id is tied to token authorization scope, and conversation_id and idempotency_key behaviors are clarified. This goes well beyond the schema field descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence states a specific verb, resource, and scope: 'Set ONE transaction's category — the write that clears a categorization gate.' It clearly distinguishes itself from sibling tools like well_set_transaction_ledger_account by narrowing to category rather than ledger account, and explicitly limits to a single transaction per call.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear context on when to call the tool: transaction_id must come from well_list_uncategorized_window, and it explicitly says to call directly when the token authorizes one workspace, and to pass workspace_id when it authorizes several. It also details when to use accepted_classifier_suggestion vs user_choice. However, it does not explicitly name alternative sibling tools (e.g., well_set_transaction_ledger_account) or state 'don't use this for ledger accounts,' so the differentiation is implicit rather than fully explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_set_transaction_ledger_accountSet a transaction's ledger accountAInspect
Attach ONE transaction to the ledger account its journal entry should post to — the write that clears a posting gap.
REQUIRED: transaction_id, from well_list_unposted_transactions. ledger_account_id — an account id from that read's ledgerCatalog, or from the row's own ledger_suggestions. Pass null to DETACH the account rather than to leave it unchanged; omitting the field is not how you clear one, because the field is required here.
Attaching the account does not, on its own, clear the gate. The worklist selects on posting attempts, not on whether an account is present, so a row you attach and leave will come back on the next read. Posting is what clears it.
Set no_invoice_expected: true to post the entry in the same call. Send it ONLY for a row whose expects_supplier_invoice is false on well_list_unposted_transactions, which is the read that carries that field: it asserts that no supplier invoice is coming, which is what makes the transaction bookable on its own. A row still waiting for its invoice must be attached WITHOUT it — the invoice is its blocker, and posting early books an entry the invoice would then contradict.
Omit the field and nothing posts: the account is recorded and the row stays on the worklist.
Most categories already imply their account — the chart maps each category key to a canonical code — so reach for this for the rows the category alone cannot settle, and for a deliberate override.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| transaction_id | Yes | The transaction to attach, from well_list_unposted_transactions. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. | |
| ledger_account_id | Yes | The ledger account to attach. `null` DETACHES the account currently on the row. | |
| no_invoice_expected | No | Assert no supplier invoice is coming, and post the entry to the attached account in the same call. Only for a row whose `expects_supplier_invoice` is false. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| posted | No | |
| success | Yes | |
| transaction_id | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| posting_outcome | No | |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only say readOnlyHint=false, so the description carries the burden of explaining side effects. It does so thoroughly: attaching alone does not clear the worklist gate, null detaches the account, omitting no_invoice_expected means nothing posts, and posting early for an invoice-pending row would create a contradiction. 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long, but the complexity justifies it. Each section earns its place: core action, required parameters, non-obvious gate behavior, the conditional posting flag, the relationship to category-based defaults, and workspace handling. The most important information is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a write tool with non-obvious semantics, the description covers prerequisites, source reads, conditional behavior, workspace ambiguity, and the difference between attaching and posting. With an output schema present, return-value details are not required. Nothing essential is missing for correct selection and invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although schema coverage is 100%, the description adds meaning well beyond the schema: transaction_id must come from well_list_unposted_transactions, ledger_account_id can come from ledgerCatalog or ledger_suggestions, null has detach semantics rather than leave-unchanged, and no_invoice_expected is only valid when expects_supplier_invoice is false. This materially improves correct invocation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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: 'Attach ONE transaction to the ledger account its journal entry should post to — the write that clears a posting gap.' It clearly distinguishes itself from sibling operations like well_set_transaction_category by explaining that this tool is for rows the category alone cannot settle or for deliberate overrides.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-to-use guidance: use it for rows the category cannot settle, or for deliberate overrides. It also gives precise conditions for no_invoice_expected, states that attaching alone does not clear the gate, and explains when workspace_id must be passed. These are actionable selection and invocation rules.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_show_accounting_settingsShow accounting settingsARead-onlyInspect
Draw the accounting-setup card so the USER reviews and confirms the workspace's accounting settings: country of incorporation, incorporation date, tax ID, fiscal year start, base currency, and accounting framework.
This is the tool for every step that asks the user to complete, review, or CONFIRM the accounting settings — the close-books settings step, an onboarding "set up your books" step. It DRAWS the card, shows each row's provenance and the stored "Suggested" fills, lets the user edit what is wrong, and waits for their Confirm. Reach for it directly on such a step; do NOT read the settings first with the silent tool and then decide to draw — drawing the card IS the step.
The card gates its Confirm on the required set (fiscal year start and base currency by default; widen it with required when a step needs more). ONLY when a step needs the values WITHOUT the user seeing a card (a silent gate check) call well_get_accounting_settings instead.
| Name | Required | Description | Default |
|---|---|---|---|
| title | No | Heading for the accounting-setup card, OVERRIDING the default wording. Use it to frame the step in its flow (e.g. "Confirm your accounting settings for the close"). At most 120 characters. Omit for the default wording. | |
| required | No | The card fields the user MUST fill before Confirm is enabled, as an exact list from ["country","incorporation_date","tax_id","fiscal_year_start_month","base_currency","accounting_framework"]. Omit to gate on the default set ["fiscal_year_start_month","base_currency"]; widen it when a step needs more (never narrow below the default). | |
| subtitle | No | Supporting line under the card's heading. At most 240 characters. Omit for the default. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| required | No | |
| settings | No | |
| next_step | No | What to do with the card this result renders. Added by the dispatcher when the card asks for a click. |
| suggestions | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already mark it read-only and non-destructive, and the description builds on that by disclosing that it shows provenance and Suggested fills, lets the user edit, waits for Confirm, and gates Confirm on the required set. It also clarifies the tool is the step itself, not a pre-read followed by a draw. 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose and each subsequent sentence adds routing or behavior guidance. It is longer than a minimal definition, but the length is justified by the interactive nature of the tool and the need to distinguish it from its silent-read sibling.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given an output schema is present, the description need not enumerate return values. It covers what the card does, when to use it, when not to, how required gating works, and which sibling to call instead. Nothing essential for selecting and invoking the tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 every parameter in detail. The description adds the default/widening behavior of the required set, but that is also present in the schema's required parameter description, so it adds little beyond the structured metadata.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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 ('Draw the accounting-setup card') and enumerates the exact settings it displays. It distinguishes itself from well_get_accounting_settings by declaring this tool draws and waits for confirmation rather than silently reading.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states explicitly that this is the tool for every step asking the user to complete, review, or confirm accounting settings, and even gives an anti-pattern: do not read first with the silent tool. It names the alternative, well_get_accounting_settings, as the only correct choice when values are needed without showing a card.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_show_company_candidatesShow the company candidatesARead-onlyInspect
Show the user the detected COMPANY candidates on a card and let them pick which company is theirs: a tile per detected company candidate with its confidence, and a company-registry search at the top for the case where none was detected.
⚠️ ONLY for the zero-company case — a membership workspace with no own company attached — when the user must choose or find the company to create the workspace from. For the values alone, read well_get_own_company, which draws nothing.
⚠️ WAIT ON THE CARD IN THE TURN THAT DREW IT. Write your one line for the user FIRST — the wait holds the turn open for up to a minute, and a user looking at a card with no sentence beside it has been given no reason to click — then call well_wait_for_selection({ kind: "company_pick" }), which this result's next_step also states. The card's own footer mints the company workspace and switches into it on the click, so never mint it yourself after the pick.
⚠️ NEVER PICK THE COMPANY for the user, and never infer it from the workspace name.
| Name | Required | Description | Default |
|---|---|---|---|
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| anchor | Yes | |
| success | Yes | |
| next_step | No | What to do with the card this result renders. Added by the dispatcher when the card asks for a click. |
| candidates | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses critical behavioral traits beyond the annotations: it draws a card that waits up to a minute, requires the agent to write a sentence first to prompt the user, and specifies that the card's footer mints and switches workspace on click. It also states the `next_step` field carries the wait instruction. These are non-obvious interaction details that an agent must know, and they align with the readOnlyHint and destructiveHint annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Though long, the description is dense with crucial information and each sentence earns its place. It leads with the core purpose, then the critical usage constraint, then the required interaction sequence, then prohibitions. The bullet-style warnings and bolded 'NEVER' items make it scannable and emphasize pitfalls. No fluff.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For an interactive card tool with a complex flow, the description covers everything an agent needs: the trigger condition, the user interaction, the timing, the sequence, the alternative tool, and the post-selection behavior. Since an output schema exists, return values are already documented. The description is complete and self-contained.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 meaningful context for `conversation_id` (explains it must be passed on every call, including card-triggered calls, and decides the conversation itself) and clarifies when `workspace_id` can be omitted. This goes beyond the schema's basic descriptions, earning a 4.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Show'), resource ('COMPANY candidates on a card'), and the user action ('pick which company is theirs'). It explicitly distinguishes itself from the sibling `well_get_own_company` by clarifying it draws a card for user selection, whereas the sibling returns values alone. This makes its purpose unmistakable and differentiated.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit when-to-use ('ONLY for the zero-company case...'), when-not-to-use ('For the values alone, read well_get_own_company'), and a clear alternative. It also details the required sequence (write a line first, wait, never mint) and prohibitions ('NEVER PICK THE COMPANY', 'never infer it'). This is textbook usage guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_show_recordsShow recordsARead-onlyInspect
Put a table of records IN FRONT OF THE USER. Use it when the user asked to SEE rows — "show me my invoices", "list my companies", "which suppliers have no category" — and when the answer you owe them IS the table.
The table is ALWAYS the root's display view in the Well web app's column order, trimmed on the widest roots to what fits a chat-width table. You never choose columns for presentation: omit fields and the right ones render.
⚠️ FOR A READ THAT IS YOURS RATHER THAN THEIRS, CALL well_query_records INSTEAD. Same arguments, same rows, no table. Every gate, count, freshness check and intermediate read belongs there — this tool renders on every call, so using it for an internal check drops a table into a conversation about something else.
⚠️ DO NOT NARRATE THE TABLE. The card already shows these rows; restating them as markdown gives the user the table and a duplicate list under it. Two things the table cannot say for itself belong in your text: totalCount when it exceeds what is displayed ("showing the 50 most recently updated of 214"), and the records_url link for everything the card truncates.
⚠️ ONE CARD PER TURN. A turn draws at most one table, and never a table beside a card that is waiting for a click.
ROOTS (read-only — all 33): companies, people, connectors, invoices, documents, transactions, accounts, payment_means, workspace_connectors, memberships, cards, checks, ledger_accounts, journals, journal_entries, tax_rates, exchange_rates, invoice_transactions, categories, account_balances, tasks, workspaces, invoice_payment_means, chat_conversations, blueprint_runs, workspace_connector_sync_logs, media, emails, phones, web_links, locations, invoice_items, billing_events (The accounting graph — ledger_accounts, journals, journal_entries — and balances/rates are read-only projections owned by the sync/posting pipelines; query them for financial context, you cannot create/update them here. Sub-resources like emails/phones/locations are usually richer when read via their parent company/person.)
CATEGORY CATALOGS: "categories" holds two independent taxonomies, separated by category_type. Always filter on it — an unfiltered read mixes them:
whereClause: { category_type: { _eq: "company" } }is the COMPANY-CATEGORY catalog: the industry labels a counterparty carries, and the idswell_update_company({ category_ids })accepts. There is no curated allowlist — the labels are minted during enrichment — so read them here rather than inventing a taxonomy.whereClause: { category_type: { _eq: "transaction" } }is the management/transaction taxonomy.
CONNECTED TOOLS: do NOT use this tool to show the user what they have connected — call well_list_connectors instead. It owns that job: connection status, and an install link for anything not connected yet. Query root "workspace_connectors" here only for genuine RECORD-level needs — reading sync timestamps, filtering connections, joining them with other roots. ("connectors" is the installable catalog; "workspace_connector_sync_logs" is per-sync history.)
Well already syncs the providers' data into the roots above — invoices, transactions, accounts, the accounting graph. ALWAYS read it from here. well_invoke_connector_tool and a provider's own tools are for an ACTION the user explicitly asked to take on that provider (e.g. "create this record in Attio"), never a way to fetch data Well already holds.
FILTERING (whereClause):
Uses Hasura-style operators on field names.
Safe operators (work on ALL field types): _eq, _neq, _in, _nin, _is_null
Numeric/date only: _gt, _gte, _lt, _lte
Text only: _like, _ilike
When unsure of a field's type, prefer _eq or _in (they always work).
Combine with _and, _or, _not
For relationship fields, use nested syntax: { "issuer": { "company_id": { "_eq": "" } } }
NEVER select the workspace's OWN records by matching a company name. One legal entity appears under several labels — a registered name, a trade name, a bank-issued label — so a name filter silently drops rows and the total reads as complete. On the invoices root, pass
partyScopeinstead: it resolves the workspace's own side on the server, so this query needs no id lookup and no extra call. Call well_get_own_company for the id only when a root has nopartyScopeand you must filter on issuer_pk / receiver_pk or the nested company_id yourself.Match a counterparty by id too whenever you have one. Reach for _ilike on a name only to DISCOVER candidates to show the user, never to compute a figure you will report. Examples: { "status": { "_eq": "unpaid" } } { "grand_total": { "_gt": 1000 } } { "local_currency": { "_eq": "EUR" } } { "_and": [{ "status": { "_eq": "unpaid" } }, { "grand_total": { "_gte": 500 } }] } { "issuer": { "company_id": { "_eq": "" } } }
SORTING (orderBy):
Sort by any field: { field: "grand_total", direction: "desc" }
Default sort is by primary key ascending.
⚠️ RULES:
Omit
fieldsto show the user a table — that is what renders the root's own columnsfieldsis ADDITIVE and for values YOU need to reason about: it widens the payload you read and never reorders or trims the columns the user seesField paths from schema: "invoices.issuer.name" → ["invoices", "issuer", "name"]
Default 50 records per request, max 500.
EXAMPLE - show the user their invoices (no fields, ever):
well_show_records({ root: "invoices", limit: 50 })
ONE CALL IS THE ANSWER — do not walk the root:
Every response carries totalCount (ALL matches, not just this page) and records_url (the full web-app table, with your filter and sort already applied). So a request to see a record type is ONE call: the user gets a table of the first page, the count tells them how many there are, and the link takes them to the rest. "Show me all my invoices" is answered by one call plus the link — NOT by fetching 483 rows into this conversation.
A non-null
nextCursoris NOT a to-do. It means more rows exist, whichtotalCountalready told you and the link already covers.Never paginate to compute a total, count, average or breakdown: aggregate over the filtered set instead. Summing a paginated sample produces a wrong number.
Never paginate to "be thorough". Large roots will exhaust the output limit mid-walk, and the user ends up with nothing legible.
Paginate ONLY for per-row work over every match that no aggregate can express, and tell the user the cost before starting. Then: pass the returned
nextCursorascursor;nextCursor: nullis the last page.
Returns { rows, totalCount, nextCursor, success }.
| Name | Required | Description | Default |
|---|---|---|---|
| root | Yes | The entity type to query — any of the 33 read-only roots (companies, people, connectors, invoices, documents, transactions, accounts, payment_means, workspace_connectors, memberships, cards, checks, ledger_accounts, journals, journal_entries, tax_rates, exchange_rates, invoice_transactions, categories, account_balances, tasks, workspaces, invoice_payment_means, chat_conversations, blueprint_runs, workspace_connector_sync_logs, media, emails, phones, web_links, locations, invoice_items, billing_events). Call well_get_schema(root) first to discover fields. | |
| limit | No | Max records to return (default 50, max 500) | |
| cursor | No | Opaque cursor for the next page. Omit for the first page, then pass nextCursor from the previous response. | |
| fields | No | EXTRA field paths to add to the root's display view, for values you need to reason about. Each path is an array whose first segment is the root's table name — use the paths well_get_schema(root) returns verbatim, which is the root name for every root except people (whose table is peoples); a path opening with any other segment is dropped. Additive only: they widen the payload you receive, and the root's own display projection (the columns the Well web app shows, and the ones a table drawn from this query carries) stays what it is no matter what you pass here. A scalar a composite renders comes back AS that composite — asking for grand_total gets you composite_total_amount_currency, with grand_total inside it — so read `columns` for what was actually materialized. Omit unless you need a value the display view does not carry. | |
| orderBy | No | Sort results by a field. Example: { field: "grand_total", direction: "desc" } | |
| allFields | No | If true, automatically fetches all scalar fields from schema. No need to specify fields. | |
| partyScope | No | Which side of an invoice the workspace itself occupies, resolved from its own company rather than a party name. `invoices` root only. "purchase" = the workspace owes it (payables); "sales" = the workspace is owed (receivables); "intra_self" = both parties are companies the workspace owns; "unattributed" = Well cannot place it on either side. The four partition every invoice, so report the "unattributed" count beside any payable total rather than dropping it — an unattributed invoice may still be owed. Prefer this over hand-writing an issuer/receiver filter. | |
| whereClause | No | Hasura-style filter object. Operators: _eq, _neq, _gt, _gte, _lt, _lte, _like, _ilike, _in, _nin, _is_null. Example: { "status": { "_eq": "unpaid" } } | |
| workspace_id | No | Target workspace. Omit to query every authorized workspace at once; each row comes back tagged with the workspace it belongs to. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes | Query results |
| error | No | |
| columns | No | The materialized columns in display order, with each composite substituted in place of the source fields it consumed. A row object's key order does not preserve this — the flattener appends reconstructed composites last — so a UI that wants the web app's column order must read it from here. |
| success | Yes | |
| returned | Yes | Number of rows returned |
| columnMeta | No | Per-column field meaning, keyed by the same column paths as the rows. `context` = what the field means; `enrichment` = how the value is sourced (e.g. Bank sync, AI extraction). Only documented columns appear. Read this to interpret the returned values. |
| nextCursor | No | Cursor for the next page. null means last page. |
| totalCount | Yes | Total matching records |
| records_url | No | Login-gated deep link to the FULL web-app records table for this root (real DataTable: composites, inline editing, resize/pin), carrying this call's `whereClause` and `orderBy` so it opens on the same rows. Hand it to the user for everything past this page — it is the answer to 'show me all of them', not pagination. Null when no workspace is in context or no web page serves the root. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint and destructiveHint annotations, the description discloses that the tool renders a table on every call, uses the app's display projection, treats fields as additive, allows at most one card per turn, and returns totalCount/records_url. It also warns against narrating the table and against paginating for aggregates — behavioral 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long and not telegraphic, but it is front-loaded with the core purpose, uses bold warnings and clear section headers, and almost every sentence carries a decision-relevant rule. It earns its length for a 10-parameter rendering tool, though some details are restated from the schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers the full 33-root set, filtering and sorting semantics, the one-call-and-link pagination model, category catalogs, connected-tool boundaries, and the return shape. Given the rich input schema and output schema, 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.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although schema coverage is 100%, the description adds substantial meaning beyond the schema: operator safety rules, nested relationship syntax, the partyScope guidance for avoiding name-based filtering traps, and the precise semantics of fields as additive rather than column-selecting. It also ties cursor to nextCursor and specifies when pagination is actually appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific behavior ('Put a table of records IN FRONT OF THE USER') and defines exactly when it applies ('when the user asked to SEE rows'). It explicitly names the sibling it is not — well_query_records — and the tool that owns connector display, well_list_connectors, so an agent can select it unambiguously.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit when-to-use guidance with quoted user utterances and explicit when-not-to-use boundaries: internal reads go to well_query_records, connector status goes to well_list_connectors, and provider-side actions go to well_invoke_connector_tool. The description even separates category catalogs and partyScope behavior, leaving little ambiguity about which tool owns which job.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_show_retargetable_connectorsShow retargetable connectorsARead-onlyInspect
Draw the connector-retarget card so the USER brings a bank (or other ledger) connector across from the membership workspace to this company workspace.
A connector connected on the parent (membership) workspace syncs its transactions there, where they cannot post. This card lists each such connector that could follow this workspace, with how strongly it was proved to belong here and how much history is behind it, pre-ticks the strong matches, and on Confirm retargets the ones the user keeps: a new connector row is created here that borrows the parent's credentials and pulls the history in on its own first sync.
Draw it on the close-books bank step to SHOW the user the connectors they can bring across, let them pick which, and CONFIRM the bring-across, once at least one candidate carries a proof tier other than "no_match" and a transaction count above zero. Drawing the card IS that step, and it waits for the user's Confirm. ONLY when a step needs the candidate count WITHOUT the user seeing a card (a silent gate check that decides whether to offer the card at all) call well_list_retargetable_connectors instead.
| Name | Required | Description | Default |
|---|---|---|---|
| title | No | Heading for the connector-retarget card, OVERRIDING the default wording. Use it to frame the step in its flow (e.g. "Bring your bank across for the close"). At most 120 characters. Omit for the default wording. | |
| subtitle | No | Supporting line under the card's heading. At most 240 characters. Omit for the default. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| next_step | No | What to do with the card this result renders. Added by the dispatcher when the card asks for a click. |
| candidates | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint annotation, the description discloses the full interaction flow: the card lists candidates, pre-ticks strong matches, waits for the user's Confirm, and on Confirm creates a new connector row that borrows the parent's credentials. It also explains the data model (parent membership workspace, transactions that cannot post there) and that drawing the card IS the step.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but information-dense)Skip; it front-loads the core action and then explains mechanics, when-to-use, and the alternative. A few phrases are slightly repetitive (e.g., 'Draw it on the close-books bank step to SHOW the user...' restates earlier points), but every paragraph earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with an output schema and rich annotations, the description covers everything needed to call it correctly: purpose, prerequisites, user-facing flow, the alternative sibling, and parameter context. Nothing critical is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and each parameter already has detailed descriptions (title overrides wording, workspace_id tells which workspace, conversation_id must be passed back). The tool description itself does not add parameter-level meaning, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'Draw the connector-retarget card' that lets the user bring a connector across workspaces. It also explains exactly what the card does (lists candidates, pre-ticks strong matches, retargets on Confirm), which distinguishes it sharply from the sibling well_list_retargetable_connectors.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description is explicit about when to use the tool: on the close-books bank step, once at least one candidate has a proof tier other than 'no_match' and a transaction count above zero. It also gives a clear exclusion, telling the agent to call well_list_retargetable_connectors instead when only a silent candidate count is needed without showing the card.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_show_workspace_pickerShow the workspace pickerARead-onlyInspect
Ask the user WHICH workspace to work in, on a card: one tile per authorized workspace, with its logo and the company behind it.
⚠️ ONLY when the token authorizes SEVERAL workspaces and no hint resolves to one. Every other case is yours to settle with well_list_workspaces, which draws nothing: exactly one workspace in the grant, a name or company the user already named, a pin this conversation itself wrote, or no workspace at all. A chooser over a set of one asks nothing, and a chooser you could have answered yourself asks the reader a question you already know the answer to.
⚠️ WAIT ON THE CARD IN THE TURN THAT DREW IT. Write your one line for the user FIRST — the wait holds the turn open for up to a minute, and a user looking at a card with no sentence beside it has been given no reason to click — then call well_wait_for_selection({ kind: "workspace" }), which this result's next_step also states. The click writes the pin server-side, so never follow it with well_switch_workspace.
⚠️ NEVER DEFAULT TO THE PRIMARY WORKSPACE on the user's behalf, and do not restate the workspaces in text under the card.
⚠️ WRITE reply IN THE USER'S LANGUAGE, WITH {picked} WHERE THE WORKSPACE NAME BELONGS. A click sends that sentence into the conversation as the person's own message, and the card puts the workspace they actually picked in place of the placeholder. A sentence left unwritten sends English to a reader who is not writing in English; a sentence that names a workspace itself is refused, because you are writing it before they have chosen.
The rows are the rows of well_list_workspaces, field for field. Its description carries the field reference, and this description does not repeat it.
| Name | Required | Description | Default |
|---|---|---|---|
| reply | No | One sentence, IN THE LANGUAGE THE USER IS WRITING IN, that the click sends into the conversation as their own message. Write what the PERSON would say about the workspace they pick, in their words, not an instruction to yourself. You write it BEFORE they click, so you cannot know what they will choose: put {picked} where the pick belongs and the card replaces it with the names they actually picked, one or several. In English it would read "Let's work on {picked}." Write the same shape in the user's language. {picked} is required: a sentence that names a pick itself names the one you guessed, so the card refuses it and sends its own English instead. At most 160 characters. Omitted sends an English sentence the card builds itself, which is wrong for any reader not writing in English. | |
| title | No | Heading for the picker card, framing the step in its flow (e.g. "Which company are we closing?"). At most 120 characters. Omit to keep the default wording; an empty string is rejected rather than rendered as a blank heading. | |
| subtitle | No | Supporting line under the picker card's heading. At most 240 characters. Omit to keep the default wording; an empty string is rejected rather than rendered blank. | |
| workspace_id | No | Target workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer. | |
| workspace_ids | No | Scope the tiles to this subset of the authorized workspaces, e.g. the company workspaces under one membership. Every id must be one this token authorizes; an id outside the grant refuses the call. Omit to draw every authorized workspace. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| session | No | What this conversation's card clicks recorded so far; null/empty fields when nothing was clicked yet. |
| success | Yes | |
| next_step | No | What to do with the card this result renders. Added by the dispatcher when the card asks for a click. |
| workspaces | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only say read-only and non-destructive; the description adds substantial behavior: the card waits up to a minute, the click writes a pin server-side, no primary workspace default, no restating tiles, and refusal of a reply that names a pick. This is exactly the kind of context annotations cannot convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The main purpose is front-loaded in one sentence, and the warning blocks are scannable and each cover a distinct trap: usage condition, waiting, no default, reply language, and data source. While long, the length is earned by the interactive card flow and the number of high-cost mistakes it prevents.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For an interactive picker tool with an output schema and complete parameter schemas, the description supplies every piece of orchestration an agent needs: when to draw the card, how to wait for the selection, what not to do afterward, and how to write the reply. There are no obvious missing behavioral or usage details.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Parameter descriptions in the schema already cover 100% of fields in detail, including the reply language, {picked} placeholder, max lengths, and conversation_id behavior. The description mostly reinforces the reply rule already present in the schema, so the baseline of 3 is appropriate; the row-definition handoff to well_list_workspaces is useful but does not add parameter-level semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence states a specific action and resource: 'Ask the user WHICH workspace to work in, on a card'. It also distinguishes itself from well_list_workspaces by specifying that it applies only when several workspaces are authorized and no hint resolves to one.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly gives the only condition for use ('ONLY when the token authorizes SEVERAL workspaces and no hint resolves to one') and names well_list_workspaces for every other case. It also directs the agent to call well_wait_for_selection and never well_switch_workspace after the click.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_sum_invoicesSum invoices over a windowARead-onlyInspect
Sum a workspace's billed amounts over a window of whole months, grouped by month, currency and billing context. Arithmetic only — this tool holds no definition of MRR or recurrence, and returns no figure the app renders.
Use it when you are computing a figure whose RULES you are stating yourself: recurring revenue over a window you chose, a total restricted to the billing contexts a reader confirmed, a per-month series behind a trend you are about to describe. The server derives no MRR of its own, so an MRR figure starts here: state the rules, sum exactly those rows, then put the result on a card with well_render_mrr.
The window is whole months. from and to are both the first day of a month, as YYYY-MM-01; from is inclusive and to is EXCLUSIVE, so June to August is 2026-06-01 to 2026-09-01. A bound inside a month is refused rather than widened, and so is a window longer than 36 months.
Which rows are billed amounts is decided here, and stated so you can say it. A canceled invoice is left out. Only billing documents count: invoices, debit notes, credit notes and subscription billing statements, so a proforma and the invoice it precedes are not summed twice, and an order, a quote or a payment advice never is. A row with no document type is read as an invoice. Every amount is NET of tax (items_total), because tax collected is owed onward rather than earned.
party_scope is required, and it decides whose invoice this is. sales is what the workspace ISSUED — its receivables, and the only side revenue can come from. purchase is what it received. The two are the same rows read from opposite ends, so no default is offered: a server choosing a side would answer a different question from the one asked. intra_self is an invoice between two companies the workspace owns, and unattributed is one Well could place on neither side.
Those four scopes partition every invoice exactly once, which is what makes an incomplete picture visible rather than silent. unattributed_count comes back on every call, whatever scope you asked for: it counts the invoices in the window that landed in that fourth bucket. State it beside any total, because an unattributed invoice may still belong in the figure and nothing here can tell you whether it does.
Every row carries ONE month, ONE currency and ONE billing context. Currency is always a grouping key, named or not: adding EUR to USD gives a number denominated in nothing, and no field on the result would tell you it happened. Convert the per-currency subtotals yourself, at a rate you can state, before you add them.
sum is already net of credit notes. A credit note subtracts its magnitude from its own month-currency-context bucket, whichever sign it was stored with; credit_note_sum and credit_note_count report what that removed, so you can say what the figure netted. Do not subtract them a second time. A bucket whose credit notes outweigh its invoices nets negative, and that is a real state rather than an error.
billing_context is null on rows that name no billing arrangement — none stored, unknown, or a value Well has no label for — and that is a third answer rather than a kind of one-off. The field is filled by extraction, not by a billing system, so a workspace can carry real recurring revenue on rows that say nothing about it. unclassified_count totals those rows. The recurring-contexts card offers them as one choice, keyed "unclassified", so apply that key to these rows and only these. Counted or not, report the count rather than letting a reader read the remainder as "everything else".
corrected_or_consolidated_count counts the corrected and consolidated invoices among the rows. Each replaces invoices Well holds no link to, so when those originals fall in the same window the sum counts that billing twice. The rows keep them, because dropping them would lose the revenue whenever the originals fall outside the window. State the count beside any total whenever it is not zero.
excluded_malformed counts billing documents in the window with no readable net amount or no currency. They are in none of the rows and none of the sums, so state the count beside any total. It comes back null when the count could not be read, which is NOT 0: zero says every row was readable, null says nobody counted.
partial: true means the aggregate was cut short and every figure is a FLOOR rather than a measurement.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| to | Yes | EXCLUSIVE end of the window: the first day of the month after the last one you want, YYYY-MM-01. | |
| from | Yes | Inclusive start of the window: the first day of a month, YYYY-MM-01. | |
| party_scope | Yes | Which side of the invoice the workspace occupies: `sales` for what it issued, `purchase` for what it received. Required; see the description. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes | |
| error | No | |
| window | Yes | |
| partial | Yes | |
| success | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| excluded_malformed | Yes | |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| unattributed_count | Yes | |
| unclassified_count | Yes | |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
| corrected_or_consolidated_count | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations only declare readOnlyHint, destructiveHint, and openWorldHint. The description adds a large amount of behavioral context: whole-month exclusivity, refusal of in-month bounds, cancellation/document-type filtering, net-of-tax semantics, party_scope partitioning, credit-note netting, null billing contexts, corrected/consolidated double counting, malformed exclusions, and partial-floor behavior. No contradiction with readOnlyHint exists since the tool is explicitly arithmetic-only.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Though long, the description is front-loaded with purpose and usage before caveats, and each paragraph is bold-labeled with a distinct operational constraint. Every paragraph carries information needed to call the tool correctly; there is no filler or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With five parameters, an output schema, and high complexity, the description covers all necessary calling details: window format, row selection, scoping, currency handling, credit notes, unclassified rows, corrected/consolidated rows, malformed rows, partial results, and authorization 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.
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 goes well beyond the schema: it explains the EXCLUSIVE window bound, that a bound inside a month is refused, that party_scope has no default because the scopes partition invoices, how workspace_id behaves under multi-workspace authorization, and the meaning of derived counts. This significantly improves parameter understanding.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific operation and resource: 'Sum a workspace's billed amounts over a window of whole months, grouped by month, currency and billing context.' It also explicitly disclaims MRR/recurrence definition, which separates it from render/MRR siblings and prevents semantic overlap.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use guidance: 'Use it when you are computing a figure whose RULES you are stating yourself,' and directs MRR flows to start here then render with well_render_mrr. It also states when workspace_id is required versus omitted. This clearly differentiates its role from alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_sum_transactionsSum transactions over a windowARead-onlyInspect
Sum a workspace's transactions over a date window, grouped how you ask. Arithmetic only — this tool holds no definition of burn, spend, or runway, and returns no figure the app renders.
Use it when you are computing a figure whose RULES you are stating yourself: a burn over a window you chose, a total that excludes categories the user named, a per-month series behind a trend you are about to describe. The server derives no burn of its own, so a burn figure starts here: state the rules, sum exactly those rows, then put the result on a card with well_render_burn.
from is inclusive and to is EXCLUSIVE, so a window of whole months passes the first instant of the month after the last one you want. Both are required: a window you cannot state is a decision you have not made, and this tool will not pick one for you.
axes groups the result (any of month, currency, category, ledger_account, category_label, transaction_type, comma-separated) and names what to group in ADDITION to currency. Every axis appears on every row: the ones you did not group by come back null, so the row shape never depends on what you asked for.
On an axis you DID name, a null is a group, not a gap. It is the rows whose column is empty, and it carries its own sums and counts like any other group. A group's rows are count_negative + count_positive, so the null group's share of that total is the part that axis cannot label. Both counts cover readable, non-zero rows only: a zero amount is in neither branch, and unreadable ones are in excluded_malformed. So the share is a share of the rows this tool could sum, not of every row in the window. What that share means, and which grouping is worth using, is yours to decide: this tool holds no view on it.
What each labelling axis IS:
ledger_account: the name on the workspace's own chart of accounts. It may have been written by an accounting sync rather than chosen by a person, so do not call it the user's own categorization without checking theledger_accountsroot for the connector that wrote it. A blank label reads asnull. A soft-deleted or inactive account still carries its name, because this axis reports what the row was labelled at the time, not what the current chart of accounts holds. It groups on the NAME, and a chart of accounts is unique on the account number rather than the name, so two accounts sharing one name arrive as a single group carrying both their sums. That is one slice per label, which is what a breakdown by label means, but it is not one slice per account: do not read a group here as an account.category: the typed catalog key, and the ONLY valueexempt_categoriesaccepts. A key the catalog no longer carries is still populated here, so it groups under a key that names nothing a reader would recognise.category_label: the stored display label, which a connector may have written in its own language. Never pass one toexempt_categories; it is not a key.transaction_type: the transaction's own type. Each value is a full sentence rather than a code, and almost every row carries one.
At most 500 groups come back, biggest first. Past that the smallest are dropped and rows_truncated is true, which is NOT partial: everything here was measured exactly and only the tail is missing. A total over a truncated result is a floor, and an axis's coverage cannot be read off one at all, because the null group may be among the dropped. Group on fewer axes, or over a shorter window, and ask again.
Currency is always grouped, named or not, so a row never mixes two. Adding EUR to USD gives a number denominated in nothing, and no field on the result would tell you it happened. Omitting axes therefore returns one row PER CURRENCY over the window, not one row. Convert the per-currency subtotals yourself, at a rate you can state, before you add them — and if there is more than one row and you report a single total without converting, the total is wrong.
Each row carries BOTH sign branches, and choosing between them is your job. sum_negative is the magnitude of the rows whose amount is negative; sum_positive is the magnitude of the rows whose amount is positive; count_negative and count_positive say how many rows are behind each. Which one is money leaving depends on the FEED, not on the query: most connectors store outflows as negatives, some store them as positive magnitudes. Read the counts to decide, and decide ONCE over the whole window rather than per row or per group: a single category or month can be all-positive on a signed feed, so electing per group flips the convention mid-answer and totals two different things together. A window whose rows are overwhelmingly negative is a signed feed, and outflow is sum_negative. Almost no negatives means the feed stores magnitudes and keeps direction in a field this tool does not group on — so it cannot separate outflow from inflow, and sum_negative + sum_positive is gross movement, not spend. Say so rather than reporting it as an outflow.
A substantial share of BOTH is a third answer, not a close call between the first two. A workspace connected to a signed feed and a magnitude feed at once pools them here, and no combination of the two subtotals is its outflow: sum_negative misses the magnitude feed's spend entirely, and adding sum_positive pulls in the signed feed's income. There is no grouping that separates them, because the axes carry no connector. Report that the window mixes conventions and that a single outflow cannot be derived from it, rather than electing whichever branch is nearer. State which convention you elected and what the counts were, so the reader can check it.
scope is required, and it decides which rows are this workspace's. own_and_adopted is the population the burn tile counts: the workspace's own transactions plus any a parent workspace shared with it through an adoption grant, with legs tested against the parent's accounts too. own is the workspace's own transactions only, tested against its own accounts — the rows its balances move with. Use own when the sum is reconciled against the workspace's own balances, as a cash-flow bridge is, and own_and_adopted for a burn. On most workspaces the two agree; on a child workspace they do not, which is why neither is a default.
exclude_internal_transfers: true keeps only the rows with EXACTLY ONE leg on an account the workspace owns. Two legs is a movement between your own accounts and drops, which is the rule's purpose. Zero legs also drops, and that is the part worth knowing: a card purchase sits against a liability account, so on a card-heavy workspace this removes card spend along with the transfers. excluded_zero_leg and excluded_multi_leg count the two populations separately, so read them before describing what the figure covers. Read excluded_zero_leg as "this many rows carried no asset movement" and nothing narrower: a card charge lands there, and so does a row whose payer and payee resolved to no account at all. excluded_no_owned_leg is that second part on its own: rows with no leg on ANY owned account, liabilities included, so card spend is never in it. Those rows could not be attributed to an account and may have moved a balance the sums cannot see, so a figure reconciled against balances treats a non-zero count as flows that are incomplete. A large excluded_zero_leg is a reason to look, never a spend total to quote. Any of the three counts comes back as null when it could not be measured, which is NOT 0: zero says the rule removed nothing, null says nobody counted. On a null, say the exclusion is unmeasured rather than reporting none — the sums themselves are unaffected, and partial is what speaks for those. For reproducing the burn tile that is exactly right — it is the conservation law the cash-flow bridge rests on. For "total spend excluding transfers between our own accounts" it is not what the words promise, so say what fell out or leave the flag off.
The rule is structural: it counts legs, so no label, category, or type on the row affects it, and a user recategorizing something does not change it.
exempt_categories takes category keys that should not count. A transaction with no category at all is never matched by an exemption and always stays in the sum; if you want those excluded too, that is a different question and you must say so.
excluded_malformed counts rows in the window whose amount could not be read as a number. They are in none of the sums, so state the count beside any total rather than presenting a figure that silently skipped them. partial: true means the aggregate measured nothing: it was cut short, or no asset account is in scope. Either way it arrives with an empty rows, so there is no figure, and the empty rows are not a zero. Say so and offer to try again, unless the workspace holds no deposit or other asset account, where a retry changes nothing.
When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
| Name | Required | Description | Default |
|---|---|---|---|
| to | Yes | EXCLUSIVE end of the window, ISO-8601 — the first instant after the last month you want. | |
| axes | No | Group the sums by these, IN ADDITION to currency. Omit for one row per currency over the whole window. | |
| from | Yes | Inclusive start of the window, ISO-8601 (e.g. 2026-06-01). | |
| scope | Yes | Which rows are this workspace's: `own` for a sum reconciled against its own balances, `own_and_adopted` for a burn. Required; see the description. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| exempt_categories | No | Category keys that do not count. An uncategorized row is never matched by one. | |
| exclude_internal_transfers | No | Keeps rows with exactly one leg on an owned account; two-leg transfers AND zero-leg rows (card purchases) both drop. See the description. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes | |
| error | No | |
| window | Yes | |
| partial | Yes | |
| success | Yes | |
| rows_truncated | Yes | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| excluded_zero_leg | Yes | |
| excluded_malformed | Yes | |
| excluded_multi_leg | Yes | |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| excluded_no_owned_leg | Yes | |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Even though readOnlyHint and destructiveHint already signal a safe read, the description discloses far more: inclusive/exclusive window semantics, null groups on named axes, truncation at 500 groups, currency always grouped, sign-branch conventions, leg-count exclusion behavior, malformed rows, and partial results. No statement 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is very long, so it is not concise in raw word count, but it is front-loaded, organized by parameter and bolded semantic warnings, and almost every paragraph carries a distinct behavioral fact. Length is mostly justified by the tool's complexity, though some editorial restatement could be trimmed.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given eight parameters, six axes, and an output schema, the description is complete enough to call correctly: it explains row shape, null vs zero meanings, truncation flags, partial rows, exclusion counts, sign-branch decisions, and multi-workspace handling. The presence of an output schema covers the remaining return-value structure.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 the schema: from/to inclusivity, axes as additions to currency, scope's reconciliation vs burn meaning, workspace_id selection behavior, conversation_id persistence, exempt_categories' non-matching of uncategorized rows, and the zero-leg drop in exclude_internal_transfers.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence names a specific verb, resource, and scope: 'Sum a workspace's transactions over a date window, grouped how you ask.' It also differentiates from rendering tools by saying 'Arithmetic only' and 'returns no figure the app renders,' which clearly separates it from siblings like well_render_burn and well_sum_invoices.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
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 when you are computing a figure whose RULES you are stating yourself,' and explains that a burn figure starts here and should then go to well_render_burn. It also gives scope-selection rules (own vs own_and_adopted), notes when workspace_id is required, and says when no prior tool call is needed.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_switch_workspaceSwitch workspaceAInspect
Write this conversation's context: the one place its standing choices live. This is the tool the widget cards call when the user CLICKS them: the workspace pin and queue, the selected months, the selected counterparties, and the step acknowledgements are all recorded here, and every later tool call defaults to them.
Pass any of:
workspace_ids (ordered list): the workspaces to work in. The FIRST entry becomes the pin and the rest the workspace_queue to work through next. Every id must be one this connection is already authorized for — call well_list_workspaces to see them. This grants no new access; it only chooses among the authorized workspaces.
periods: the months the user VALIDATED on the period card ({ calendar_year, calendar_month } each). Period-scoped reads (well_list_missing_invoices, well_preview_invoice_fetch) default to them when called without a period. Send it only for the user's month selection — never to bound a counterparty pick, which would overwrite that selection.
counterparties: the counterparties (vendors) the user selected, each { company_id, matched_connector_service_id }. Copy both ids off the row you listed them from; pass no display name. The selection belongs to the workspace this call is dispatched to, and a switch to another workspace clears it. One conversation holds one selection, so a new one replaces it; a selection sent for a workspace this conversation has switched away from is REFUSED instead, so a card the flow moved past cannot overwrite the pinned workspace's selection.
counterparty_periods: the months the counterparties card listed, sent alongside counterparties. The pick then narrows those months only, and a month it never covered is read in full. With none named, the months this conversation already holds bound the pick. This never becomes the conversation's selected months.
exempt_categories: the category keys the user marked as NOT burn on the exemption card, copied off what well_list_burn_exemptions returned. An EMPTY list is a real answer and records that they exempted nothing; omit the field entirely when they have not answered yet. The set belongs to the workspace this call is dispatched to, and a switch to another workspace clears it, but a change of months does not, because a category is or is not burn for the business whatever window is read next. One conversation holds one set, so a new one replaces it.
recurring_contexts: the billing context keys the user counts as recurring revenue on the recurring-contexts card, copied off what well_list_recurring_contexts returned. An EMPTY list is a real answer and records that they count nothing as recurring. It is scoped, cleared and replaced exactly like exempt_categories.
counted_account_types: the account types the user ticked as cash on the cash-scope card, copied off what well_list_cash_scope returned. An EMPTY list is a real answer and records that they count nothing as cash, which ends the run rather than reporting a zero total. It is scoped, cleared and replaced exactly like exempt_categories.
ack: "connectors", "bank", "categorize", "assign", "deploy", "invite" or "company_pick": records that the user answered that flow step, in the workspace this call is dispatched to. "company_pick" is the company candidates card's own ack: the card sends it on its Use click alongside the pin to the new company workspace, and on its Keep for later without moving the pin, so you do not send it yourself. "categorize", "deploy", "retarget", "company_pick" and "invite" also take ack_outcome: "done" when the user carried the step out, "keep_for_later" when they set it aside; both end the step. A switch to another workspace clears it, so the next workspace's card asks for its own click. An acknowledgement sent for a workspace this conversation has switched away from is REFUSED instead, so a card the flow moved past cannot un-confirm the step the pinned workspace's own card recorded.
next_step: the line the user picked on the next-steps card, as { skill, prompt } copied off that card's row. The prompt is the sentence the model reads as the user's own message, so it travels beside the slug rather than being rebuilt from it. The pick belongs to the workspace this call is dispatched to, and a switch to another workspace clears it.
Call it with NO argument at all to pin the workspace this call itself is dispatched to: its universal workspace_id, or the only workspace the token covers. workspace_ids is the PIN write and nothing else: it moves the pin AND replaces the workspace_queue, so a one-entry list ends a run that still had workspaces queued. Send it only to change the workspace. A call carrying periods, counterparties, exempt_categories, recurring_contexts, counted_account_types, ack or next_step needs no workspace_ids: its universal workspace_id targets that one call, and the pin and the queue stay where they are. Never re-pin the workspace this conversation already holds.
Every provided input is applied, and changed names the conversation fields this call wrote. pickup says what became of the write: "resumed" or "held_then_resumed" mean the model's own turn carries it on and the caller must send no reply of its own, "unwaited" or "exhausted" mean nothing is watching and the caller's reply is the only thing that moves the flow, and "stale" means the flow already answered this card, so the value is kept as a late edit and the caller says nothing. resumed is the boolean half of the first two. After a switch, every later call that omits workspace_id targets the pinned workspace, for reads and writes alike; passing workspace_id on a later call overrides it for that call only. well_list_workspaces reports the current conversation context, and well_wait_for_selection reads a card click back, instantly when it already landed here and after a short wait otherwise.
This changes nothing in the user's data.
| Name | Required | Description | Default |
|---|---|---|---|
| ack | No | Acknowledge a flow step: "connectors" for the connect-tools step, "bank" for the bank step, "categorize" for the counterparty-categorization step, "assign" for the assign-owners step, "deploy" for the collect-agents step, "retarget" for the connector-retarget (data-migration) card, "company_pick" for the company-candidates card, "invite" for the invite-members card. "retarget" is the retarget card's own ack: the card sends it on its Confirm with ack_outcome "done", or on its Keep for later with "keep_for_later", so you do not send it yourself. The acknowledgement is scoped to the workspace this call is dispatched to, and a switch to another workspace clears it. An acknowledgement sent for a workspace this conversation has switched away from is refused, and the pinned workspace's own acknowledgement of that step stands. | |
| periods | No | The months the user VALIDATED on the period card, stored as this conversation's selected_periods. Later period-scoped reads default to them when called without a period. This field is the user's month selection alone: to bound a counterparty pick, send counterparty_periods instead. | |
| next_step | No | The next step the user picked on the well_propose_next_steps card, as the pair that card listed: the skill's slug and the sentence beside it. Both travel, because the sentence is what the model reads as the user's own message and the slug is what it loads. The pick is scoped to the workspace this call is dispatched to, and a switch to another workspace clears it. | |
| ack_outcome | No | What the click said about the step: "done" when the user carried it out, "keep_for_later" when they set it aside on purpose. REQUIRED with ack "categorize" and "deploy" and "retarget" and "company_pick" and "invite", whose cards end on two buttons that mean different things, and refused with every other step, whose card confirms and nothing else. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| workspace_ids | No | Ordered workspace selection, and the ONLY field that moves the pin: the FIRST entry becomes this conversation's pin, and the rest REPLACE the workspace_queue, so a one-entry list empties a queue that still holds workspaces. Every entry must be authorized for this connection, or the whole call is refused. Never send it to name the workspace of a periods, counterparties or ack call: the universal workspace_id already targets those, while a re-pin to the id this conversation already holds writes nothing and clears the queue. | |
| counterparties | No | The counterparties the user selected, stored as this conversation's selected_counterparties and scoped to the workspace this call is dispatched to. At most 200, and a longer list is refused, so a select-all keeps to that bound. Copy the ids off the row: company_id, plus matched_connector_service_id when the row carries one. Send the card's months as counterparty_periods in the same call, so the pick applies to those months only; with none named, the months this conversation already holds bound it. The conversation holds ONE selection, so this REPLACES the previous one, but only when the call names the pinned workspace: a selection sent for a workspace this conversation has switched away from is refused, and the pinned workspace's selection stands. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. | |
| exempt_categories | No | The categories the user marked as NOT burn, stored as this conversation's selected_exemptions and scoped to the workspace this call is dispatched to. Copy each category_key off the row well_list_burn_exemptions returned; never send a label. An EMPTY list is a real answer, since it records that the user exempted nothing, so send it when they clicked Continue with nothing ticked, and omit the field entirely when they have not answered. At most 100, the same bound well_sum_transactions accepts, so a recorded answer is always one the sum can run. The conversation holds ONE set, so this REPLACES the previous one; unlike a counterparty pick it survives a change of months, because a category is or is not burn for the business whatever window is read next. | |
| recurring_contexts | No | The billing contexts the user counts as recurring revenue, stored as this conversation's selected_recurring_contexts and scoped to the workspace this call is dispatched to. Copy each context_key off the row well_list_recurring_contexts returned; never send a label. An EMPTY list is a real answer, since it records that the user counts nothing as recurring, so send it when they clicked Continue with nothing ticked, and omit the field entirely when they have not answered. At most 100, the same bound well_render_mrr accepts. The conversation holds ONE set, so this REPLACES the previous one, and like the exemptions it survives a change of months. | |
| counterparty_periods | No | The months the counterparties card listed, which bound the pick sent as counterparties in the same call. Send it only with counterparties; it never becomes this conversation's selected months, so it cannot overwrite what the user validated on the period card. With none named, the months this conversation already holds bound the pick. | |
| counted_account_types | No | The account types the user ticked as cash, stored as this conversation's selected_cash_scope and scoped to the workspace this call is dispatched to. Copy each account_type off the group well_list_cash_scope returned; never send a label. An EMPTY list is a real answer, since it records that the user counts nothing as cash, which ends the run rather than reporting a zero, so send it when they clicked Continue with nothing ticked, and omit the field entirely when they have not answered. The conversation holds ONE set, so this REPLACES the previous one, and like the exemptions it survives a change of months: a credit card is or is not cash for the business whatever window is read next. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| pickup | Yes | What became of this write: "resumed" (a model wait took it), "held_then_resumed" (no wait yet, so the call was held until one consumed it), "unwaited" (nothing ever consumed it), "exhausted" (the model gave up on the card and ended the turn), "stale" (the flow already answered this card and moved past it — the value is recorded as a late edit). A card prefills a reply on "unwaited" and "exhausted" only. |
| changed | No | Which fields of this conversation's context this call wrote. |
| resumed | Yes | True when the model's own turn carries this write on — the derived half of `pickup` ("resumed" or "held_then_resumed"). A card that reads true must not prefill a reply; the conversation continues on its own. |
| success | Yes | |
| warning | No | Present when this call opened a fresh lane because no conversation_id reached it: the write landed, and only a later call that passes the echoed conversation_id back can read it. |
| acknowledged | No | |
| workspace_id | No | |
| workspace_name | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| workspace_queue | No | The workspaces queued after the pinned one, in order. |
| selected_periods | No | |
| selected_next_step | No | The next step this call recorded, echoed back as the card sent it. |
| selected_cash_scope | No | |
| selected_exemptions | No | |
| acknowledged_outcome | No | The answer the acknowledging click carried, echoed back on a step whose card offers two. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
| selected_counterparties | No | |
| selected_recurring_contexts | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only indicate readOnlyHint=false and destructiveHint=false, so the description carries the behavioral burden and handles it thoroughly. It discloses that writes are refused for workspaces switched away from, that empty lists are meaningful answers, that scope is cleared on workspace switch but survives month changes, and that 'This changes nothing in the user's data.' It also explains the pickup return states and whether the caller must 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose and then per-field semantics, which is appropriate for a tool with 13 parameters and complex scoping rules. It is long and somewhat repetitive with the input schema, restating empty-list and scoping rules for three nearly identical fields, but each sentence does add operational guidance. It could be tightened without losing value, but it is not padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a highly complex, stateful tool with 13 parameters and no required parameters, the description covers all key dimensions: what each field stores, scoping and clearing rules, refusal behavior, the meaning of return fields (changed, pickup, resumed), and the effect of a switch on subsequent calls. It even explains when the caller should not reply. 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.
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 goes far beyond the schema: it explains that the first workspace_ids entry becomes the pin and the rest replace the queue, that a one-entry list empties the queue, that empty lists for exempt_categories/recurring_contexts/counted_account_types are real answers, that counterparty_periods never becomes the selected months, and that stale acks are refused. These are essential semantic distinctions the schema alone does not convey.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence states a specific verb and resource: 'Write this conversation's context — the one place its standing choices live.' It goes on to enumerate exactly what is recorded (workspace pin/queue, months, counterparties, step acknowledgements) and explicitly frames this as the tool widget cards call. This clearly differentiates it from the other well_* tools, which either read context or perform domain operations.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
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: 'Call it with NO argument at all to pin the workspace', 'Send it only to change the workspace', 'Never re-pin the workspace this conversation already holds', and it explains that scoped writes such as periods or ack require no workspace_ids. It also references the related tools well_list_workspaces and well_wait_for_selection to route the agent correctly.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_update_companyUpdate companyADestructiveInspect
Update an existing company in the current workspace.
Use this tool when the user asks to change, fix, rename, or edit a company's fields.
REQUIRED: company_id OPTIONAL (only include fields the user wants changed): name, description, domain, registered_name, trade_name, tax_id_value, tax_id_type, registry_country (ISO 3166-1 alpha-2, e.g. "FR"), business_type, registered_value, registry_name, locale (ISO 639-1 two-letter language code, e.g. "en", "fr" — not "en_US").
CATEGORIES (a counterparty's industry): pass category_ids — the COMPLETE set
of category ids the company should carry. It REPLACES the current set: ids you
leave out are unlinked, and [] clears every category. Omit the field to
leave the categories untouched. Read the catalog first with
well_query_records({ root: "categories", whereClause: { category_type: { _eq:
"company" } } }) and pass ids from it — an id that is not a
category_type = "company" row is refused, and this tool never creates a
category.
NOT CHANGEABLE via this tool: emails, phones, locations, linked people, media. Those require dedicated tools (not yet available).
PROVENANCE: decision says HOW the set was chosen. accepted_suggestion —
the user let a category the classifier had already proposed stand, without
touching it. explicit — the user chose the labels.
A request the user typed is always an explicit choice, so never send
accepted_suggestion from a conversation. The affirmation belongs to the
categorization card, where a pre-filled picker the reader leaves alone is the
only thing that can be let stand; a user who names a category in words has
chosen it, even when they say they agree with a suggestion. Omit the field
and the write is explicit.
The server checks an accepted_suggestion claim against the company's own
pending proposals and returns explicit when the written set matches none
of them, so the claim can never manufacture classifier provenance.
Returns { success: true, company_id, name } on success — plus category_count,
the number of categories the company carries afterwards, and decision, the
provenance the server settled on, when the call passed category_ids. Returns
{ success: false, error } on failure.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | Company name | |
| domain | No | Primary website domain (e.g. acme.com) | |
| locale | No | Preferred language as an ISO 639-1 two-letter code (e.g. en, fr, de). Pass null to clear. | |
| decision | No | How the user arrived at `category_ids`. Omit it: a request the user typed is an explicit choice, and accepted_suggestion belongs to the categorization card. See the description. | |
| company_id | Yes | The UUID of the company to update (required) | |
| trade_name | No | Trading name / DBA | |
| description | No | Brief company description; pass null to clear | |
| tax_id_type | No | Tax identifier type (VAT, SIRET, EIN, ...) | |
| category_ids | No | The COMPLETE set of company-category ids this company should carry. Replaces the current set; [] clears it; omit to leave categories unchanged. | |
| tax_id_value | No | Tax identifier value (VAT, SIRET, EIN, ...) | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| business_type | No | Business type / legal form | |
| registry_name | No | Registry name | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. | |
| registered_name | No | Official registered legal name | |
| registered_value | No | Registry identifier value | |
| registry_country | No | ISO 3166-1 alpha-2 country code of the registry (e.g. FR, US) | |
| ledger_default_source | No | How the AP/AR pick was made. The only value this write accepts is `human_override`: the person chose the account from the chart of accounts. Optional, and it defaults to `human_override`, so a plain assign needs it not at all. | |
| account_payable_default_id | No | The counterparty's default account-payable ledger account (a vendor payable, FR PCG 401). Set it for a counterparty you pay. Omit to leave it; null clears it. Read the ids with `well_list_ledger_accounts`. | |
| account_receivable_default_id | No | The counterparty's default account-receivable ledger account (a customer receivable, FR PCG 411). Set it for a counterparty that pays you. Omit to leave it; null clears it. Must differ from the payable default. |
Output Schema
| Name | Required | Description |
|---|---|---|
| name | No | |
| error | No | |
| success | Yes | |
| decision | No | |
| company_id | No | |
| category_count | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations provide destructiveHint=true and readOnlyHint=false; the description adds precisely what the annotations cannot: category_ids REPLACES the whole set (unlinking omitted ids), [] clears all, and null clears individual fields — concretely specifying what 'destructive' means here. It also discloses the server-side provenance check (an accepted_suggestion claim that matches no pending proposal is downgraded to explicit) and idempotency/retry behavior. 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
Well-structured with bold section headers, front-loaded purpose, and required/optional summary; at roughly 650 words for a 21-parameter tool, the length is mostly earned. The provenance section repeats the explicit-vs-accepted_suggestion distinction three times and could be tightened, which prevents a 5.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For the most complex tool in the set — 21 params, full-replacement category semantics, provenance validation, multi-workspace writes — the description covers when to use it, what not to touch, prerequisites, exact success shape ({ success: true, company_id, name } plus category_count and decision), and failure shape. Nothing an agent needs to call it correctly is left to guesswork.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% (baseline 3), yet the description carries semantics the schema omits or defers: the decision schema literally says 'See the description' and the description delivers the full provenance rules; category_ids gets the validation rule (ids must be category_type='company' or they are refused; the tool never creates categories); workspace_id gets its multi-workspace ambiguity explained ('a write lands in exactly one workspace and this call would not say which'). This materially changes how the agent invokes the tool.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource+scope: 'Update an existing company in the current workspace,' with an explicit trigger set ('change, fix, rename, or edit a company's fields'). The 'existing' qualifier separates it from well_create_company, and the NOT CHANGEABLE list (emails, phones, locations, linked people, media) draws a clear boundary against sibling tools. An agent can distinguish this from the ~85 siblings immediately.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Contains an explicit when-to-use sentence ('Use this tool when the user asks to change, fix, rename, or edit...'), an explicit when-not list ('NOT CHANGEABLE via this tool: emails, phones... require dedicated tools'), and a named prerequisite/alternative tool (read the categories catalog first with well_query_records). This is textbook-explicit routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_update_invoiceUpdate invoiceADestructiveInspect
Update an existing invoice in Well.
Call well_get_schema("invoices") to discover all available fields.
REQUIRED: invoice_id OPTIONAL (only pass fields you want changed):
reference_number, issue_date (ISO date), due_date (ISO date)
status (draft | issued | paid | canceled)
terms, description
grand_total, items_total, tax_total (numbers)
local_currency (ISO 4217 three-letter code, e.g. "EUR", "USD")
document_type_code (UN/CEFACT 1001 code, e.g. "380")
billing_context (e.g. subscription, one_time, project, ...)
issuer_company_id / receiver_company_id (uuid to set, null to clear, omit to leave unchanged)
Cannot change line items, payment_means, or document attachment via this tool.
| Name | Required | Description | Default |
|---|---|---|---|
| terms | No | Payment terms text; pass null to clear | |
| status | No | Invoice lifecycle status | |
| due_date | No | Payment due date (ISO 8601); pass null to clear | |
| tax_total | No | Total tax amount; pass null to clear | |
| invoice_id | Yes | The UUID of the invoice to update | |
| issue_date | No | Issue date (ISO 8601, e.g. 2026-04-27); pass null to clear | |
| description | No | Free-form description; pass null to clear | |
| grand_total | No | Total invoice amount including tax; pass null to clear | |
| items_total | No | Sum of line items before tax; pass null to clear | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| local_currency | No | ISO 4217 three-letter currency code (e.g. EUR, USD); pass null to clear | |
| payment_status | No | User-driven payment_status override — requires override_version (CAS) | |
| billing_context | No | Billing context / business model; pass null to clear | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. | |
| override_version | No | Required when payment_status is present — current override_version for CAS | |
| reference_number | No | Invoice reference number (e.g. INV-2026-001); pass null to clear | |
| issuer_company_id | No | Issuer company UUID. Omit = no change, null = clear, uuid = set. | |
| document_type_code | No | UN/CEFACT 1001 document type code (e.g. 380 for commercial invoice); pass null to clear | |
| receiver_company_id | No | Receiver company UUID. Omit = no change, null = clear, uuid = set. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| invoice_id | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| reference_number | No | |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag the operation as destructive and non-read-only; the description adds useful behavioral detail: null clears a field, omitted fields remain unchanged, and several invoice components are immutable. This meaningfully extends beyond what the annotations alone convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured: summary, schema discovery hint, required parameter, optional parameter list, then unsupported fields. Every line carries actionable information, and the hierarchy makes it easy to scan.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's 20 parameters, the schema and output schema carry substantial information. The description covers the key invocation logic, partial-update semantics, and exclusions. Conditional requirements such as workspace_id are only in schema descriptions, but those descriptions are thorough, so the overall package is complete enough.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 grouping parameters into required vs. optional, clarifying null/omit semantics for issuer_company_id and receiver_company_id, and supplying concrete enum examples. Some nuances are left to the schema, but the description compensates well.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description starts with a specific verb and resource: 'Update an existing invoice in Well.' It clearly distinguishes this from create/delete siblings by focusing on existing invoices and by explicitly stating what cannot be changed (line items, payment_means, document attachment).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides action-oriented guidance: call well_get_schema('invoices') to discover fields, pass only fields you want changed, and avoid unsupported fields. It does not explicitly name alternative tools or say 'use X instead,' but the context makes the intended use clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_update_personUpdate personADestructiveInspect
Update an existing person (contact) in the current workspace.
Use this tool when the user asks to change, fix, rename, or edit a person's fields.
REQUIRED: person_id OPTIONAL (only include fields the user wants changed): first_name, last_name, job_title.
NOT CHANGEABLE via this tool: emails, phones, locations, linked companies, media. Those require dedicated tools (not yet available).
Returns { success: true, person_id, full_name } on success, or { success: false, error } on failure.
| Name | Required | Description | Default |
|---|---|---|---|
| job_title | No | Job title; pass null to clear | |
| last_name | No | Last name | |
| person_id | Yes | The UUID of the person to update (required) | |
| first_name | No | First name | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| full_name | No | |
| person_id | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark this as destructive (destructiveHint=true) and read-only false, but the description goes beyond that. It specifies that only certain fields can be updated (partial update), lists non-changeable fields, and details the exact return format on success and failure. This is rich behavioral disclosure that helps the agent understand side effects and constraints.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is structured with clear sections: purpose, usage, required/optional, not changeable, and returns. It is front-loaded with the core action and uses bullet-like formatting for readability. It is slightly verbose but every sentence adds value; 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.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers all essential aspects for calling the tool correctly: the exact purpose, when to use it, what fields to include, what fields are excluded, and the return format. The schema handles workspace/conversation/idempotency details, and an output schema exists. Nothing critical is missing for an update operation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema covers 100% of parameters with descriptions, so the baseline is 3. The description adds meaning by distinguishing required vs. optional person fields and instructing to 'only include fields the user wants changed,' which is not in the schema. It also clarifies that emails, phones, locations, etc. are not changeable. It does not elaborate on workspace_id, conversation_id, or idempotency_key, but those are adequately described in the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's action: 'Update an existing person (contact) in the current workspace.' It uses a specific verb and resource, and explicitly distinguishes itself from related tools by mentioning 'change, fix, rename, or edit a person's fields.' It also differentiates from create and delete siblings by saying 'existing' and focusing on update semantics.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit when-to-use guidance: 'Use this tool when the user asks to change, fix, rename, or edit a person's fields.' It also lists what is NOT changeable and states that those require dedicated tools, effectively indicating when not to use it. This clearly differentiates it from well_create_person and well_delete_person.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_upload_documentUpload documentAInspect
Upload a document (invoice, receipt, statement) into the workspace by sending its bytes as base64.
⚠️ THIS IS A WIDGET'S WRITE, NOT YOURS. The card's drop zone reads the file the person dropped or chose, encodes it, and calls this tool itself. Do NOT call it: a model holds no file, so a call made from a conversation can only carry bytes nobody supplied. When a person says they have the invoice, point them at the drop zone on the gap card.
Send content_base64 WITHOUT a data-URI prefix — the raw base64 only, no data:application/pdf;base64, header.
Accepted content: PDF, JPEG, PNG, GIF, HEIC, HEIF, AVIF, WEBP, TIFF, plain text, CSV, XML. The bytes are checked against the declared mime_type (file signature, not just the claim), so a PNG announced as a PDF is refused.
Size ceiling: 5 MB of file (before base64). A larger file is refused with its actual size — upload it through the web app instead, which accepts up to 15 MB.
Pass source_transaction_id to anchor the document to the bank transaction it pays. That is what makes a dropped invoice land on the right line instead of in a general inbox.
Well extracts the document after upload; the extraction is asynchronous and this call returns as soon as the file is stored. A file already in the workspace is deduplicated by content and returns the existing document rather than a copy.
| Name | Required | Description | Default |
|---|---|---|---|
| filename | Yes | The file's name WITH its extension, e.g. `invoice-2026-03.pdf`. The extension resolves the content type when `mime_type` is generic. | |
| mime_type | Yes | The file's content type, e.g. `application/pdf`. Send `application/octet-stream` when unknown and the extension decides. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| content_base64 | Yes | The file's bytes, base64, with no data-URI prefix. | |
| source_task_id | No | The task this document answers. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. | |
| source_transaction_id | No | The bank transaction this document is the proof for. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| outcome | No | |
| success | Yes | |
| filename | No | |
| max_bytes | No | |
| mime_type | No | |
| error_code | No | |
| size_bytes | No | |
| document_id | No | |
| actual_bytes | No | |
| deduplicated | No | |
| source_task_id | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| source_transaction_id | No | |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond annotations (readOnly=false, destructive=false), the description discloses numerous behavioral traits: the tool is intended for widget-only calls, the bytes are checked against the declared mime_type via file signature, the file is deduplicated by content, extraction is asynchronous and the call returns immediately after storage, and oversized files are refused with actual size. These details give the agent a complete mental model of the operation's side effects and constraints.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but every sentence adds value. The critical warning is front-loaded right after the purpose statement tree. Content is organized into logical paragraphs: widget-only usage, encoding rules, accepted types, size limit, anchoring, and async behavior. There is no fluff or repetition; it is an efficient, well-structured explanation.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (8 parameters, write operation, async behavior, deduplication, size constraints), the description covers all necessary operational details. It complements the output schema (which explains return values) and the rich parameter descriptions. Nothing an agent needs to decide whether to call this tool, or to avoid calling it, is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Even though schema coverage is 100%, the description adds crucial semantics not present in the schema: content_base64 must be sent without a data-URI prefix, mime_type is validated against file signature (not just the claim), filename extension resolves content type when mime_type is generic, and source_transaction_id anchors the document to a bank transaction so it lands on the right line. This goes well beyond the schema's property descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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: 'Upload a document (invoice, receipt, statement) into the workspace by sending its bytes as base64.' It clearly identifies the resource (document), the destination (workspace), and the mechanism (base64 bytes). It also distinguishes itself from sibling tools like well_upload_statement_bytes and well_create_invoice_document by listing document types and emphasizing the widget-driven flow.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly tells the agent when NOT to use the tool: 'THIS IS A WIDGET'S WRITE, NOT YOURS. Do NOT call it' and explains why ('a model holds no file'). It provides an alternative action for the agent ('point them at the drop zone on the gap card'). It also gives a size-based alternative ('upload it through the web app instead, which accepts up to 15 MB'), making the routing decision fully unambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_upload_statement_bytesUpload statement bytesAInspect
Upload a bank statement file's BINARY CONTENT (PDF or image) as base64, so the file's real bytes reach Well without any out-of-band HTTP call.
Use it for PDF and image statements up to 5 MiB decoded (the base64 text may be roughly a third larger). Base64-encode the file's bytes EXACTLY — never re-encode a screenshot, a transcription, or a summary of the file. Optionally send the file's sha256 (hex); the server decodes, hashes, and rejects a mismatch, proving the bytes arrived intact.
The response carries content_sha256 and byte_length of the decoded payload — report them for verification. The parsed rows, totals, and import outcome arrive via well_get_statement_import_result with the returned document_id.
Text statements (.csv/.txt/.xml) whose contents are verbatim in this conversation can go through well_upload_statement_content instead. Upload one statement file per call — call this tool once per file. The document enters the same import pipeline as an in-app upload (detection, dedup, promotion).
| Name | Required | Description | Default |
|---|---|---|---|
| sha256 | No | The source file's SHA-256 (hex). When sent, a mismatch with the decoded bytes rejects the upload. | |
| filename | Yes | The statement's file name, e.g. "statement.csv". Only its extension selects the format. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| content_base64 | Yes | The file's bytes, base64-encoded (RFC 4648; whitespace tolerated). Decoded cap: 5 MiB. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| byte_length | No | |
| document_id | No | Poll well_get_statement_import_result with this id for the import outcome. |
| deduplicated | No | True when an identical document was already in the workspace — nothing was imported twice. |
| content_sha256 | No | SHA-256 (hex) of the payload the server received — compare against your source to verify fidelity. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint=false and destructiveHint=false; the description carries substantial behavioral disclosure: it is a write that lands in exactly one workspace, it enforces a 5 MiB decoded cap, verifies sha256 and rejects mismatches, and feeds the same import pipeline (detection, dedup, promotion) as an in-app upload. It also discloses what happens after upload (rows/totals via well_get_statement_import_result) and instructs reporting content_sha256/byte_length for verification. This far exceeds 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long (three paragraphs) but justified by the tool's complexity — binary encoding, verification, pipeline behavior, and workspace semantics. It is front-loaded with the core purpose and constraints, and every sentence carries distinct information. Could trim minor redundancy around the pipeline mention, but overall efficient.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 6-parameter mutation tool with an output schema, the description is remarkably complete: size limits, encoding exactness, integrity verification, response/verification instructions, sibling routing, per-call usage, workspace disambiguation, and post-upload outcome flow are all covered. 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.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, giving a baseline of 3, but the description adds real meaning: it clarifies that base64 must encode the file's exact bytes ('never re-encode a screenshot, a transcription, or a summary'), explains the sha256 mismatch-rejection flow, and contextualizes the workspace_id ambiguity. This is meaningful value beyond the schema's field docs, earning a 4.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a precise verb+resource statement: 'Upload a bank statement file's BINARY CONTENT (PDF or image) as base64'. It explicitly differentiates from the sibling well_upload_statement_content by contrasting binary (PDF/image) vs text (.csv/.txt/.xml) statements, and names the alternative tool directly. An agent can distinguish this from all ~80 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is explicit: 'Use it for PDF and image statements up to 5 MiB decoded' and 'Text statements (.csv/.txt/.xml) whose contents are verbatim in this conversation can go through well_upload_statement_content instead.' It also states the one-call-per-file rule and notes when workspace_id must be supplied. Both the when and the when-not/alternative are spelled out.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_upload_statement_contentUpload statement contentAInspect
Upload a bank statement's TEXT CONTENT (a .csv, .txt, or .xml file) directly, as an alternative to well_create_statement_upload's out-of-band file POST.
Use it when the user's statement is a small text file whose contents are verbatim in this conversation (1 MiB decoded limit). Send the content EXACTLY as you received it — never reformat, summarize, transcribe from memory, or reconstruct rows. A mangled relay imports wrong financial data.
This path is BEST-EFFORT fidelity: what Well ingests is what you relayed, not a byte-verified copy of the user's file. The response carries content_sha256 and byte_length of what the server received — report them so a corrupted relay is visible. The parsed rows, totals, and import outcome arrive via well_get_statement_import_result with the returned document_id, not in this response.
PDFs and images NEVER go here (the model cannot relay their bytes faithfully) — use well_upload_statement_bytes. XML with DOCTYPE/ENTITY declarations is rejected. Upload one statement file per call — call this tool once per file. The document enters the same import pipeline as an in-app upload (detection, dedup, promotion).
| Name | Required | Description | Default |
|---|---|---|---|
| filename | Yes | The statement's file name, e.g. "statement.csv". Only its extension selects the format. | |
| content_text | Yes | The file's full text content, verbatim. UTF-8 encoded on the wire; capped at 1 MiB decoded. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| success | Yes | |
| byte_length | No | |
| document_id | No | Poll well_get_statement_import_result with this id for the import outcome. |
| deduplicated | No | True when an identical document was already in the workspace — nothing was imported twice. |
| content_sha256 | No | SHA-256 (hex) of the payload the server received — compare against your source to verify fidelity. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only mark readOnlyHint=false and destructiveHint=false. The description goes far beyond by warning 'this path is BEST-EFFORT fidelity' and instructing the agent to report content_sha256 and byte_length so a corrupted relay is visible. It also discloses that parsed rows arrive via well_get_statement_import_result, not in this response, and that it enters the same import pipeline. This adds critical behavioral context not available from 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but every sentence earns its place. It front-loads the core purpose and usage condition, then covers exclusions, limitations, and response handling in a logical order. While it could be trimmed slightly, the density of actionable detail justifies the length without being redundant.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (5 params, one binary sibling, an out-of-band alternative, and a downstream import result), the description covers everything an agent needs to call it correctly: exact-content relay, size cap, rejection rules, one-per-file constraint, how to obtain the import result, and how to handle workspace/conversation scoping. The presence of an output schema further reduces the burden, yet the description still explains what to expect and report.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 material value beyond the schema: it mandates 'Send the content EXACTLY as you received it — never reformat, summarize, transcribe from memory, or reconstruct rows,' explains that the filename extension selects format, clarifies workspace_id selection when multiple workspaces are authorized, and specifies how conversation_id should be sourced. These are operational semantics an agent could not infer from the parameter names alone.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens by stating a specific verb and resource: 'Upload a bank statement's TEXT CONTENT' and positions it as an alternative to well_create_statement_upload's out-of-band POST. It explicitly distinguishes it from well_upload_statement_bytes for binary files, so an agent can immediately tell which of the two sibling tools to use based on file type.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit conditions for use: 'Use it when the user's statement is a small text file whose contents are verbatim in this conversation (1 MiB decoded limit).' It also states clear exclusions: 'PDFs and images NEVER go here' and names the alternative tool, plus rejects XML with DOCTYPE/ENTITY declarations. Even the one-file-per-call rule is spelled out. Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_upsert_accounting_settingsSet accounting settingsADestructiveInspect
Set the workspace's accounting settings: fiscal year start month, first fiscal year start date, country, base currency, accounting framework, chart-of-accounts confirmation, the incorporation date, and the tax ID (value and type together).
Provide only the fields you are changing; omitted fields are left untouched. An empty call (no fields) is refused. tax_id_value and tax_id_type must be provided together.
Only a workspace owner or admin may set the accounting settings. A caller without that role is refused, not silently ignored.
Changing the fiscal year start month moves the whole fiscal calendar, so it is REFUSED when a period is locked or a close is in progress — the tool surfaces that refusal rather than forcing it. When the change is allowed, it soft-deletes the workspace's regenerable DRAFT journal entries so they re-mint on the new coordinates; VALIDATED and LOCKED entries are never touched.
These are accounting-critical values. Confirm each one with the user before calling and never guess them — do not infer a country, currency, framework, start month, or tax ID the user did not state.
The tax ID here updates the workspace's anchored company and its settings mirror together, so the two never drift. To set WHICH company is anchored, use well_set_own_company; to set that company's tax ID, use this tool.
| Name | Required | Description | Default |
|---|---|---|---|
| country | No | ISO 3166-1 alpha-2 country code. | |
| tax_id_type | No | The tax id's type (SIREN, VAT, EIN, …). Provide it together with tax_id_value. | |
| tax_id_value | No | The company's tax id value. Provide it together with tax_id_type; one without the other is refused. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which. | |
| base_currency | No | ISO 4217 currency code. | |
| coa_confirmed | No | Whether the chart of accounts has been confirmed. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| idempotency_key | No | Optional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation. | |
| incorporation_date | No | The company's incorporation / registration date as YYYY-MM-DD, or null to clear it. | |
| accounting_framework | No | The accounting framework the books follow. | |
| fiscal_year_start_month | No | Calendar month (1-12) the fiscal year starts on, or null to clear it. | |
| first_fiscal_year_start_date | No | First fiscal year start date as YYYY-MM-DD, or null to clear it. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| country | No | |
| success | Yes | |
| tax_id_type | No | |
| tax_id_value | No | |
| base_currency | No | |
| coa_confirmed | No | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| incorporation_date | No | |
| accounting_framework | No | |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
| fiscal_year_start_month | No | |
| first_fiscal_year_start_date | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations' destructiveHint, the description discloses substantial behavior: partial updates leave omitted fields untouched, fiscal-year changes are refused when periods are locked or closes are in progress, and allowed changes soft-delete only regenerable DRAFT journal entries while leaving VALIDATED and LOCKED entries untouched. It also explains that the tax ID updates the anchored company and its settings mirror together.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but dense and front-loaded: the first sentence states the purpose and enumerates the affected fields, and every subsequent sentence adds operational or safety-relevant guidance. It avoids redundant schema restatement and remains readable despite covering many edge cases.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's destructive hint, 12 parameters, and no required fields, the description is unusually complete: it covers partial-update semantics, refusal conditions, role requirements, side effects on journal entries, user-confirmation expectations, and the relationship to a sibling tool. The output schema exists, so lack of return-value explanation is acceptable.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the description does not need to restate parameter formats. It adds valuable interaction semantics the schema cannot express: omitted fields are left untouched, an empty call is refused, tax_id_value and tax_id_type must be provided together, and changing fiscal_year_start_month has side effects on journal entries.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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 the workspace's accounting settings' followed by an explicit list of the fields involved. It also distinguishes itself from the closely related sibling well_set_own_company by stating that this tool sets the tax ID while the sibling sets which company is anchored.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit invocation conditions: only workspace owners or admins may call it, unauthorized callers are refused, and empty calls are refused. It also gives an alternative ('To set WHICH company is anchored, use well_set_own_company') and strong guidance to confirm values with the user before calling.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_wait_for_selectionWait for selectionARead-onlyInspect
Read the user's card click, holding the turn open until it lands. Call it in the SAME turn, right after the tool whose card asks the user to click: well_list_workspaces (kind "workspace"), well_list_periods (kind "periods"), well_list_missing_invoices (kind "counterparties" — its card is the only one that records a counterparty pick), well_list_burn_exemptions (kind "exemptions"), well_list_recurring_contexts (kind "recurring_contexts"), well_list_cash_scope (kind "cash_scope"), well_list_connectors (kind "connect_ack" for the connect step, "bank_ack" for the bank step), well_list_counterparties (kind "categorize_ack" — its Continue and its Keep for later both write it), well_list_missing_invoice_owners (kind "assign_ack" — its Continue writes it), well_preview_invoice_fetch (kind "deploy_ack" — its Deploy, its Continue and its Keep for later all write it), well_show_company_candidates (kind "company_pick" — its Use this company mints the company workspace, switches into it and writes the ack in one call; its Keep for later writes the same ack with the outcome the click carried and moves no pin), well_show_retargetable_connectors (kind "retarget_ack" — its Confirm and its Keep for later both write it, with the outcome the click carried), well_list_member_candidates (kind "invite_ack" — its Send and its Keep for later both write it, with the outcome the click carried), or well_propose_next_steps (kind "next_step": a row click writes it; on "selected", take selection.next_step.prompt as the user's own message and start that skill in the same turn, loading it with well_get_skill). It waits up to 60s for the click. "selected" — continue the flow. "no_selection_yet" — call it again at once, at most 5 calls in this turn; after the fifth, end the turn on the card in one line, and the user's click then prefills the reply that resumes the flow.
status "selected": the choice is recorded.
selectioncarries it — the pinned workspace_id and workspace_queue, the picked periods, the picked counterparties (each { company_id, matched_connector_service_id } plus the workspace_id they belong to and theperiodsthey were listed for), the exempted category keys or the recurring context keys plus the workspace_id they belong to, or the acknowledgement plus the workspace_id it was made in and, on a card whose buttons say different things, theoutcomethe click carried.already_set: truemeans it was recorded since the card was drawn but before this call (the user had already clicked). Continue the flow with it.
Only a click recorded SINCE the card was drawn is reported. An answer left over from an earlier conversation stays in the session and is never handed back, so this tool always waits for the click the card in front of the user is asking for.
status "no_selection_yet": nothing has been recorded since the card was drawn and no click landed within the wait (default 60s, clamped 5-60s). This is a NORMAL result, not an error. Call this tool again at once, up to 5 calls in one turn. After the fifth, end the turn on the card in one line; the user's click then prefills the reply that resumes the flow.
A counterparty pick belongs to the workspace AND the months it was made against, and it carries those months in selection.periods. A switch to another workspace, a change of the selected months, or a fresh well_list_missing_invoices card drops it. So kind "counterparties" never hands back a pick made against another month: with that pick dropped, the call waits for the new click instead. A pick recorded BEFORE this call is reported only when it was made in the workspace this call targets, so pass workspace_id to ask about a workspace the conversation is not switched to. A pick that lands DURING the wait rides back with the workspace it was made in. Compare selection.workspace_id before you act on it.
A burn-exemption answer belongs to the workspace it was made in and rides back as selection.workspace_id, and it carries no months: a category is or is not burn for the business, so the same answer holds over any window. An EMPTY selection.exempt_categories on status "selected" means the user exempted NOTHING — act on it, do not re-ask. A switch to another workspace drops it; a change of the selected months does not.
A recurring-contexts answer follows the same rules as a burn-exemption answer: it belongs to the workspace it was made in, carries no months, and an EMPTY selection.recurring_contexts on status "selected" means the user counts NOTHING as recurring — act on it, do not re-ask.
A cash-scope answer follows those same rules, and its empty case is the one to read carefully: an EMPTY selection.counted_account_types on status "selected" means the user counts NOTHING as cash. That is a resolution, not a scope of size zero — say there is no cash position left to report and END the run, rather than carrying an empty scope into a total the renderer cannot draw.
Kind "cash_scope" is the reader's ANSWER — the account types they ticked, and nothing else. It is not well_render_cash_forecast's cash_scope field, which is the fuller policy an answer feeds into (the counted types plus the ownership and exclusion counts the caller measured).
An acknowledgement belongs to the workspace it was made in, and rides back as selection.workspace_id. On kinds "categorize_ack", "deploy_ack", "retarget_ack", "company_pick" and "invite_ack" it also carries selection.outcome: "done" means the user carried the step out, "keep_for_later" means they set it aside. Both end the step, so continue the flow either way and say in half a sentence which one it was. A switch to another workspace drops it. An ack recorded BEFORE this call is reported only when it was made in the workspace this call targets, so pass workspace_id to ask about a workspace the conversation is not switched to. A click that lands DURING the wait is reported with its own workspace, which can be another card's. Compare selection.workspace_id before you act on it.
This tool reads and waits — it changes nothing.
| Name | Required | Description | Default |
|---|---|---|---|
| kind | Yes | Which card click to wait for: "workspace" (the workspace picker's Use), "periods" (the month picker's Validate), "counterparties" (the missing-invoices card's Continue), "exemptions" (the burn-exemption card's Continue), "recurring_contexts" (the recurring-contexts card's Continue), "cash_scope" (the cash-scope card's Continue), "accounting_settings_ack" (the accounting-settings card's Confirm), "connect_ack" / "bank_ack" (the connect card's Continue), "categorize_ack" (the categorize card's Continue or Keep for later), "assign_ack" (the owner-assignment card's Continue), "deploy_ack" (the collect-agents card's Deploy, Continue or Keep for later), "company_pick" (the company-candidates card's Use this company or Keep for later), "invite_ack" (the invite-members card's Send or Keep for later), "retarget_ack" (the connector-retarget card's Confirm or Keep for later), "next_step" (a row click on the next-steps card). | |
| timeout_s | No | How long to wait, in seconds. Default 60, clamped to 5-60. | |
| workspace_id | No | Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did. | |
| waiting_notice | No | One short line the person reads while this call holds the turn open, IN THE LANGUAGE THEY ARE WRITING IN. Say what you are waiting for them to do on the card, in your own words, not what the server is doing: they are the one holding the flow. Omitted falls back to an English line, which a reader working in another language may not read, so write it whenever you know their language. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. |
Output Schema
| Name | Required | Description |
|---|---|---|
| hint | No | |
| kind | No | |
| error | No | |
| status | No | |
| success | Yes | |
| selection | No | The value the click wrote. Present only when status is "selected". |
| already_set | No | True when the click had already been recorded since the card was drawn, before this call started — no wait happened. |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. |
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. |
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. |
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description goes far beyond the readOnlyHint and destructiveHint annotations: it discloses the 60s wait, the clamping, the no-stale-click guarantee, the workspace/period drop rules, the meaning of empty selection arrays, and the explicit closing statement 'This tool reads and waits — it changes nothing.' No behavioral surprise is hidden.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded and organized into readable sections, but it is much longer than necessary. Scoping rules for counterparties, exemptions, recurring contexts, cash scope, and acknowledgements repeat the same workspace/period concepts multiple times, making the text harder to scan than the underlying logic warrants.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with many kinds and edge cases, the description is exhaustive: it covers statuses, selection payload shapes, empty-selection semantics, workspace scoping, acknowledgement outcomes, next_step skill handoff, and timeout behavior. Despite having an output schema, the description still explains the return semantics in enough detail to act correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although schema coverage is 100%, the description adds substantial meaning beyond the schema: it explains workspace_id targeting and fallback behavior, waiting_notice localization and fallback, conversation_id propagation rules, and the semantic distinctions among ack kinds and outcomes. This is not a repetition of the schema; it is operational guidance.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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: 'Read the user's card click, holding the turn open until it lands.' It then enumerates the exact predecessor tools and kinds, so an agent can distinguish this wait/read tool from the many well_list_* and well_show_* 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly says when to call the tool: 'Call it in the SAME turn, right after the tool whose card asks the user to click,' and then maps each predecessor to its kind. It also specifies the no_selection_yet retry policy, the five-call limit, and the end-turn fallback, leaving no ambiguity about invocation context.
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.
87 tool updates
- Changed
well_add_contact_channel4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_assign_account4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_assign_missing_invoice_owners4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_claim_statement_draft4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_create_company4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_create_company_candidate4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_create_company_workspace4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_create_invoice_document4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_create_invoice_from_data4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_create_person4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_create_statement_upload4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_delete_company4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_delete_invoice4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_delete_person4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_enqueue_invoice_fetch4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_get_accounting_settings4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_get_connector_coverage7 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - changed
Input schema / properties / from_selection / descriptionPrevious value: -"Scope the card to the connectors behind the counterparties the user picked on the missing-invoices card this session, each installable one pre-checked. Use it for the connect step that FOLLOWS a vendor pick. It returns those vendors' connectors and NOTHING else: a picked connector that brings no invoices in is dropped, and no accounting or invoicing tool the user did not pick is offered here — that offer is its own step, scoped with `kind`. Cannot be combined with `q` or `kind` — those name a catalog to browse, and this names a set already decided. Returns an empty list when the session holds no pick for this workspace, or when no picked counterparty matched a connector."New value: +"Scope the card to the connectors behind the counterparties the user picked on the missing-invoices card in this conversation, each installable one pre-checked. Use it for the connect step that FOLLOWS a vendor pick. It returns those vendors' connectors and NOTHING else: a picked connector that brings no invoices in is dropped, and no accounting or invoicing tool the user did not pick is offered here: that offer is its own step, scoped with `kind`. Cannot be combined with `q` or `kind`: those name a catalog to browse, and this names a set already decided. Returns an empty list when this conversation holds no pick for this workspace, or when no picked counterparty matched a connector." - added
Input schema / properties / include_unsent_countsAdded value: +{ + "const": true, + "description": "Add the top-level `unsent_document_counts`: every tool Well forwards this workspace's documents to, each with how many documents it has not received yet. That array is the whole answer — it holds every one of the workspace's outbound connections, whatever catalog page was requested, and the rows carry no count at all. Off by default, because it costs an extra read. The figure is the workspace's whole backlog, not a period's.", + "type": "boolean" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +} - added
Output schema / properties / unsent_document_countsAdded value: +{ + "description": "Every tool this workspace forwards documents to, each with the documents it has not received yet, biggest backlog first. Present only when `include_unsent_counts` was set. THIS IS THE ONLY PLACE THE BACKLOG IS REPORTED: it is not a page, it carries the workspace's whole set of outbound connections whether or not the requested catalog page holds their rows, and the catalog rows carry no count. An empty array means the workspace forwards to nothing; an absent field means nothing was measured. `unsent_document_count_is_upper_bound: true` means a document filter applies to that connection, so the count is a maximum and reads as `up to <n>`. Every count is read off Well's own forward record, so it measures what Well wrote down rather than what the tool confirmed. Name a tool by its `name`. An entry reading `0` is a tool that is up to date: say nothing about it.", + "items": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "service_id": { + "type": "string" + }, + "unsent_document_count": { + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "unsent_document_count_is_upper_bound": { + "type": "boolean" + }, + "workspace_connector_id": { + "type": "string" + } + }, + "required": [ + "service_id", + "name", + "workspace_connector_id", + "unsent_document_count", + "unsent_document_count_is_upper_bound" + ], + "type": "object" + }, + "type": "array" +}
- Changed
well_get_customer_einvoicing_details4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_get_design_tokens4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_get_entity4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Added
well_get_graph - Changed
well_get_investment_holdings4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_get_own_company4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_get_schema4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_get_session_digest4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_get_skill5 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +} - changed
Output schema / properties / reason / anyOfPrevious value: -[ - { - "enum": [ - "skill_unknown", - "catalog_unreadable" - ], - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "enum": [ + "skill_unknown", + "catalog_unreadable", + "write_skill_in_app" + ], + "type": "string" + }, + { + "type": "null" + } +]
- Changed
well_get_statement_import_result4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_get_worklist_status4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_invite_members4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_invoke_connector_tool4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_list_account_balances5 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - changed
Input schema / properties / months_back / descriptionPrevious value: -"How many closed month ends to carry per account, oldest first. Omit for the current reading alone."New value: +"How many complete month ends to carry per account, oldest first. Omit for the current reading alone." - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_list_accounts_needing_company4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_list_burn_exemptions4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_list_cash_scope4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_list_connector_tools4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_list_connectors7 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - changed
Input schema / properties / from_selection / descriptionPrevious value: -"Scope the card to the connectors behind the counterparties the user picked on the missing-invoices card this session, each installable one pre-checked. Use it for the connect step that FOLLOWS a vendor pick. It returns those vendors' connectors and NOTHING else: a picked connector that brings no invoices in is dropped, and no accounting or invoicing tool the user did not pick is offered here — that offer is its own step, scoped with `kind`. Cannot be combined with `q` or `kind` — those name a catalog to browse, and this names a set already decided. Returns an empty list when the session holds no pick for this workspace, or when no picked counterparty matched a connector."New value: +"Scope the card to the connectors behind the counterparties the user picked on the missing-invoices card in this conversation, each installable one pre-checked. Use it for the connect step that FOLLOWS a vendor pick. It returns those vendors' connectors and NOTHING else: a picked connector that brings no invoices in is dropped, and no accounting or invoicing tool the user did not pick is offered here: that offer is its own step, scoped with `kind`. Cannot be combined with `q` or `kind`: those name a catalog to browse, and this names a set already decided. Returns an empty list when this conversation holds no pick for this workspace, or when no picked counterparty matched a connector." - added
Input schema / properties / include_unsent_countsAdded value: +{ + "const": true, + "description": "Add the top-level `unsent_document_counts`: every tool Well forwards this workspace's documents to, each with how many documents it has not received yet. That array is the whole answer — it holds every one of the workspace's outbound connections, whatever catalog page was requested, and the rows carry no count at all. Off by default, because it costs an extra read. The figure is the workspace's whole backlog, not a period's.", + "type": "boolean" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +} - added
Output schema / properties / unsent_document_countsAdded value: +{ + "description": "Every tool this workspace forwards documents to, each with the documents it has not received yet, biggest backlog first. Present only when `include_unsent_counts` was set. THIS IS THE ONLY PLACE THE BACKLOG IS REPORTED: it is not a page, it carries the workspace's whole set of outbound connections whether or not the requested catalog page holds their rows, and the catalog rows carry no count. An empty array means the workspace forwards to nothing; an absent field means nothing was measured. `unsent_document_count_is_upper_bound: true` means a document filter applies to that connection, so the count is a maximum and reads as `up to <n>`. Every count is read off Well's own forward record, so it measures what Well wrote down rather than what the tool confirmed. Name a tool by its `name`. An entry reading `0` is a tool that is up to date: say nothing about it.", + "items": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "service_id": { + "type": "string" + }, + "unsent_document_count": { + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "unsent_document_count_is_upper_bound": { + "type": "boolean" + }, + "workspace_connector_id": { + "type": "string" + } + }, + "required": [ + "service_id", + "name", + "workspace_connector_id", + "unsent_document_count", + "unsent_document_count_is_upper_bound" + ], + "type": "object" + }, + "type": "array" +}
- Changed
well_list_counterparties9 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Input schema / properties / missing_ledger_onlyAdded value: +{ + "const": true, + "description": "A workspace-wide sweep for the counterparties that have a bank transaction and still need a ledger account (COA) default set for the direction their transactions take — the ledger-assignment worklist. It is its OWN scope: never pass it with `periods`, `uncategorized_only`, or `cursor`. Each row carries `needs_payable`/`needs_receivable` and the AP/AR defaults it holds now (a needed slot is empty), and rides in `ledger_rows` rather than `rows`.", + "type": "boolean" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +} - added
Output schema / properties / ledger_rowsAdded value: +{ + "items": { + "additionalProperties": {}, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "type": "array" +} - changed
Output schema / properties / mode / descriptionPrevious value: -"Which SCOPE the call asked for; the row fields that are populated follow from it. `periods` whenever the call named months, whether or not it also filtered to the uncategorized ones."New value: +"Which SCOPE the call asked for; the row fields that are populated follow from it. `periods` whenever the call named months, whether or not it also filtered to the uncategorized ones. `missing_ledger_only` returns its rows in `ledger_rows`, not `rows`." - changed
Output schema / properties / mode / enumPrevious value: -[ - "periods", - "uncategorized_only" -]New value: +[ + "periods", + "uncategorized_only", + "missing_ledger_only" +] - changed
Output schema / properties / total_count / descriptionPrevious value: -"Counterparties MATCHING the call, before the row cap, so a capped sweep says what it left out. On the periods scope: the DISTINCT counterparties `rows` names, and a multi-month call lists one counterparty on one row per month, so row_count can exceed it. Under `uncategorized_only` it counts the uncategorized ones alone; the scope's whole population is `categorized_count` plus `uncategorized_count`."New value: +"Counterparties MATCHING the call, before the row cap, so a capped sweep says what it left out. On the periods scope: the DISTINCT counterparties `rows` names, and a multi-month call lists one counterparty on one row per month, so row_count can exceed it. Under `uncategorized_only` it counts the uncategorized ones alone; the scope's whole population is `categorized_count` plus `uncategorized_count`. Under `missing_ledger_only` it is the worklist actually returned and equals `row_count`: that scope reads the first 500 counterparties needing a default, so a worklist that fills 500 is a FLOOR, not a complete count. Assign those and read the scope again for the rest."
- Added
well_list_ledger_accounts - Changed
well_list_member_candidates4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_list_missing_invoice_owners4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_list_missing_invoices4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_list_periods4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_list_recurring_contexts4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_list_retargetable_connectors4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_list_uncategorized_window4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Added
well_list_unposted_journals - Changed
well_list_unposted_transactions4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_list_workspaces5 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +} - changed
Output schema / properties / session / descriptionPrevious value: -"What this session's card clicks recorded so far; null/empty fields when nothing was clicked yet."New value: +"What this conversation's card clicks recorded so far; null/empty fields when nothing was clicked yet."
- Changed
well_preview_invoice_fetch4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_propose_next_steps4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_query_records4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_remove_contact_channel4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_render_burn4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_render_cash_flow_bridge4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_render_cash_forecast4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_render_cash_position4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_render_cost_structure4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_render_mrr4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_render_runway4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Added
well_repost_journals - Changed
well_resolve_reconciliation_task4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_resolve_register_diff_gap4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_retarget_connectors4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_run_register_diff4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_search_company_registry4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_search_context4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_search_skill5 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +} - changed
Output schema / properties / reason / anyOfPrevious value: -[ - { - "enum": [ - "skill_unknown", - "catalog_unreadable" - ], - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "enum": [ + "skill_unknown", + "catalog_unreadable", + "write_skill_in_app" + ], + "type": "string" + }, + { + "type": "null" + } +]
- Added
well_set_counterparty_default_category - Changed
well_set_own_company4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_set_transaction_category4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_set_transaction_ledger_account4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_show_accounting_settings4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_show_company_candidates4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_show_records4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_show_retargetable_connectors4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_show_workspace_picker5 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +} - changed
Output schema / properties / session / descriptionPrevious value: -"What this session's card clicks recorded so far; null/empty fields when nothing was clicked yet."New value: +"What this conversation's card clicks recorded so far; null/empty fields when nothing was clicked yet."
- Changed
well_sum_invoices4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_sum_transactions4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_switch_workspace14 fields changed- changed
Input schema / properties / ack / descriptionPrevious value: -"Acknowledge a flow step: \"connectors\" for the connect-tools step, \"bank\" for the bank step, \"categorize\" for the counterparty-categorization step, \"assign\" for the assign-owners step, \"deploy\" for the collect-agents step, \"retarget\" for the connector-retarget (data-migration) card, \"company_pick\" for the company-candidates card, \"invite\" for the invite-members card. \"retarget\" is the retarget card's own ack: the card sends it on its Confirm with ack_outcome \"done\", or on its Keep for later with \"keep_for_later\", so you do not send it yourself. The acknowledgement is scoped to the workspace this call is dispatched to, and a switch to another workspace clears it. An acknowledgement sent for a workspace this connection has switched away from is refused, and the pinned workspace's own acknowledgement of that step stands."New value: +"Acknowledge a flow step: \"connectors\" for the connect-tools step, \"bank\" for the bank step, \"categorize\" for the counterparty-categorization step, \"assign\" for the assign-owners step, \"deploy\" for the collect-agents step, \"retarget\" for the connector-retarget (data-migration) card, \"company_pick\" for the company-candidates card, \"invite\" for the invite-members card. \"retarget\" is the retarget card's own ack: the card sends it on its Confirm with ack_outcome \"done\", or on its Keep for later with \"keep_for_later\", so you do not send it yourself. The acknowledgement is scoped to the workspace this call is dispatched to, and a switch to another workspace clears it. An acknowledgement sent for a workspace this conversation has switched away from is refused, and the pinned workspace's own acknowledgement of that step stands." - added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - changed
Input schema / properties / counted_account_types / descriptionPrevious value: -"The account types the user ticked as cash, stored as this session's selected_cash_scope and scoped to the workspace this call is dispatched to. Copy each account_type off the group well_list_cash_scope returned; never send a label. An EMPTY list is a real answer — it records that the user counts nothing as cash, which ends the run rather than reporting a zero — so send it when they clicked Continue with nothing ticked, and omit the field entirely when they have not answered. The session holds ONE set, so this REPLACES the previous one, and like the exemptions it survives a change of months: a credit card is or is not cash for the business whatever window is read next."New value: +"The account types the user ticked as cash, stored as this conversation's selected_cash_scope and scoped to the workspace this call is dispatched to. Copy each account_type off the group well_list_cash_scope returned; never send a label. An EMPTY list is a real answer, since it records that the user counts nothing as cash, which ends the run rather than reporting a zero, so send it when they clicked Continue with nothing ticked, and omit the field entirely when they have not answered. The conversation holds ONE set, so this REPLACES the previous one, and like the exemptions it survives a change of months: a credit card is or is not cash for the business whatever window is read next." - changed
Input schema / properties / counterparties / descriptionPrevious value: -"The counterparties the user selected, stored as this session's selected_counterparties and scoped to the workspace this call is dispatched to. At most 200 — a longer list is refused, so a select-all keeps to that bound. Copy the ids off the row: company_id, plus matched_connector_service_id when the row carries one. Send the card's months as counterparty_periods in the same call, so the pick applies to those months only; with none named, the months this session already holds bound it. The session holds ONE selection, so this REPLACES the previous one — but only when the call names the pinned workspace: a selection sent for a workspace this connection has switched away from is refused, and the pinned workspace's selection stands."New value: +"The counterparties the user selected, stored as this conversation's selected_counterparties and scoped to the workspace this call is dispatched to. At most 200, and a longer list is refused, so a select-all keeps to that bound. Copy the ids off the row: company_id, plus matched_connector_service_id when the row carries one. Send the card's months as counterparty_periods in the same call, so the pick applies to those months only; with none named, the months this conversation already holds bound it. The conversation holds ONE selection, so this REPLACES the previous one, but only when the call names the pinned workspace: a selection sent for a workspace this conversation has switched away from is refused, and the pinned workspace's selection stands." - changed
Input schema / properties / counterparty_periods / descriptionPrevious value: -"The months the counterparties card listed, which bound the pick sent as counterparties in the same call. Send it only with counterparties; it never becomes this session's selected months, so it cannot overwrite what the user validated on the period card. With none named, the months this session already holds bound the pick."New value: +"The months the counterparties card listed, which bound the pick sent as counterparties in the same call. Send it only with counterparties; it never becomes this conversation's selected months, so it cannot overwrite what the user validated on the period card. With none named, the months this conversation already holds bound the pick." - changed
Input schema / properties / exempt_categories / descriptionPrevious value: -"The categories the user marked as NOT burn, stored as this session's selected_exemptions and scoped to the workspace this call is dispatched to. Copy each category_key off the row well_list_burn_exemptions returned; never send a label. An EMPTY list is a real answer — it records that the user exempted nothing — so send it when they clicked Continue with nothing ticked, and omit the field entirely when they have not answered. At most 100, the same bound well_sum_transactions accepts, so a recorded answer is always one the sum can run. The session holds ONE set, so this REPLACES the previous one; unlike a counterparty pick it survives a change of months, because a category is or is not burn for the business whatever window is read next."New value: +"The categories the user marked as NOT burn, stored as this conversation's selected_exemptions and scoped to the workspace this call is dispatched to. Copy each category_key off the row well_list_burn_exemptions returned; never send a label. An EMPTY list is a real answer, since it records that the user exempted nothing, so send it when they clicked Continue with nothing ticked, and omit the field entirely when they have not answered. At most 100, the same bound well_sum_transactions accepts, so a recorded answer is always one the sum can run. The conversation holds ONE set, so this REPLACES the previous one; unlike a counterparty pick it survives a change of months, because a category is or is not burn for the business whatever window is read next." - changed
Input schema / properties / periods / descriptionPrevious value: -"The months the user VALIDATED on the period card, stored as this session's selected_periods. Later period-scoped reads default to them when called without a period. This field is the user's month selection alone — to bound a counterparty pick, send counterparty_periods instead."New value: +"The months the user VALIDATED on the period card, stored as this conversation's selected_periods. Later period-scoped reads default to them when called without a period. This field is the user's month selection alone: to bound a counterparty pick, send counterparty_periods instead." - changed
Input schema / properties / recurring_contexts / descriptionPrevious value: -"The billing contexts the user counts as recurring revenue, stored as this session's selected_recurring_contexts and scoped to the workspace this call is dispatched to. Copy each context_key off the row well_list_recurring_contexts returned; never send a label. An EMPTY list is a real answer — it records that the user counts nothing as recurring — so send it when they clicked Continue with nothing ticked, and omit the field entirely when they have not answered. At most 100, the same bound well_render_mrr accepts. The session holds ONE set, so this REPLACES the previous one, and like the exemptions it survives a change of months."New value: +"The billing contexts the user counts as recurring revenue, stored as this conversation's selected_recurring_contexts and scoped to the workspace this call is dispatched to. Copy each context_key off the row well_list_recurring_contexts returned; never send a label. An EMPTY list is a real answer, since it records that the user counts nothing as recurring, so send it when they clicked Continue with nothing ticked, and omit the field entirely when they have not answered. At most 100, the same bound well_render_mrr accepts. The conversation holds ONE set, so this REPLACES the previous one, and like the exemptions it survives a change of months." - changed
Input schema / properties / workspace_ids / descriptionPrevious value: -"Ordered workspace selection, and the ONLY field that moves the pin: the FIRST entry becomes this connection's pin, and the rest REPLACE the workspace_queue — so a one-entry list empties a queue that still holds workspaces. Every entry must be authorized for this connection, or the whole call is refused. Never send it to name the workspace of a periods, counterparties or ack call: the universal workspace_id already targets those, while a re-pin to the id this connection already holds writes nothing and clears the queue."New value: +"Ordered workspace selection, and the ONLY field that moves the pin: the FIRST entry becomes this conversation's pin, and the rest REPLACE the workspace_queue, so a one-entry list empties a queue that still holds workspaces. Every entry must be authorized for this connection, or the whole call is refused. Never send it to name the workspace of a periods, counterparties or ack call: the universal workspace_id already targets those, while a re-pin to the id this conversation already holds writes nothing and clears the queue." - changed
Output schema / properties / changed / descriptionPrevious value: -"Which session fields this call wrote."New value: +"Which fields of this conversation's context this call wrote." - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +} - added
Output schema / properties / warningAdded value: +{ + "description": "Present when this call opened a fresh lane because no conversation_id reached it: the write landed, and only a later call that passes the echoed conversation_id back can read it.", + "type": "string" +}
- Changed
well_update_company7 fields changed- added
Input schema / properties / account_payable_default_idAdded value: +{ + "anyOf": [ + { + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The counterparty's default account-payable ledger account (a vendor payable, FR PCG 401). Set it for a counterparty you pay. Omit to leave it; null clears it. Read the ids with `well_list_ledger_accounts`." +} - added
Input schema / properties / account_receivable_default_idAdded value: +{ + "anyOf": [ + { + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The counterparty's default account-receivable ledger account (a customer receivable, FR PCG 411). Set it for a counterparty that pays you. Omit to leave it; null clears it. Must differ from the payable default." +} - added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Input schema / properties / ledger_default_sourceAdded value: +{ + "description": "How the AP/AR pick was made. The only value this write accepts is `human_override`: the person chose the account from the chart of accounts. Optional, and it defaults to `human_override`, so a plain assign needs it not at all.", + "enum": [ + "human_override" + ], + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_update_invoice4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_update_person4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_upload_document4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_upload_statement_bytes4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_upload_statement_content4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_upsert_accounting_settings4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
- Changed
well_wait_for_selection4 fields changed- added
Input schema / properties / conversation_idAdded value: +{ + "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.", + "type": "string" +} - added
Output schema / properties / conversation_idAdded value: +{ + "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.", + "type": "string" +} - added
Output schema / properties / conversation_id_noteAdded value: +{ + "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.", + "type": "string" +} - added
Output schema / properties / conversation_id_sourceAdded value: +{ + "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.", + "enum": [ + "host_meta", + "argument", + "minted" + ], + "type": "string" +}
8 tool updates
- Changed
well_claim_statement_draft9 fields changed- changed
Input schema / properties / claim_token / descriptionPrevious value: -"The claim token from the user's message, exactly as written. Single-use, expires one hour after the drop."New value: +"The drop's claim token, exactly as written: from the argument after /import-statement or from the user's message. Single-use, covers every statement in that drop, expires one hour after the drop." - removed
Output schema / properties / byte_lengthRemoved value: -{ - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" -} - removed
Output schema / properties / content_sha256Removed value: -{ - "description": "SHA-256 (hex) of the payload the server received — compare against your source to verify fidelity.", - "type": "string" -} - removed
Output schema / properties / deduplicatedRemoved value: -{ - "description": "True when an identical document was already in the workspace — nothing was imported twice.", - "type": "boolean" -} - removed
Output schema / properties / document_idRemoved value: -{ - "description": "Poll well_get_statement_import_result with this id for the import outcome.", - "type": "string" -} - added
Output schema / properties / documentsAdded value: +{ + "default": [], + "description": "One entry per file that reached the import pipeline.", + "items": { + "additionalProperties": false, + "properties": { + "byte_length": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "content_sha256": { + "type": "string" + }, + "deduplicated": { + "description": "True when an identical document was already in the workspace, so nothing was imported twice.", + "type": "boolean" + }, + "document_id": { + "description": "Poll well_get_statement_import_result with this id for the import outcome.", + "type": "string" + }, + "draft_id": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "ordinal": { + "description": "The file's 0-based index among the files Well accepted from the drop, in arrival order.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "draft_id", + "ordinal", + "filename", + "document_id", + "content_sha256", + "byte_length", + "deduplicated" + ], + "type": "object" + }, + "type": "array" +} - added
Output schema / properties / drop_idAdded value: +{ + "description": "The drop this token belonged to. Safe to quote; the token is not.", + "type": "string" +} - added
Output schema / properties / failedAdded value: +{ + "default": [], + "description": "Files that were claimed but did not ingest. Their bytes are gone; ask the user for exactly these by name.", + "items": { + "additionalProperties": false, + "properties": { + "draft_id": { + "type": "string" + }, + "error": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "ordinal": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "draft_id", + "ordinal", + "filename", + "error" + ], + "type": "object" + }, + "type": "array" +} - changed
Output schema / requiredPrevious value: -[ - "success" -]New value: +[ + "success", + "documents", + "failed" +]
- Removed
well_get_cost_structure - Changed
well_invite_members2 fields changed- added
Input schema / properties / notifyAdded value: +{ + "description": "Send the invitation email now. Defaults to true. Pass false to create or reissue the pending membership WITHOUT emailing, when a later explicit step sends it — e.g. assigning an owner by a typed email, then sending the invite from the invite card. Applies to a workspace target only; a group invite always notifies.", + "type": "boolean" +} - added
Output schema / properties / results / items / properties / person_idAdded value: +{ + "description": "The invitee's person id on a successful workspace invite (sent or reissued). Use it to assign the person as an owner right after inviting, with no second lookup. Absent on a refusal and on a group invite.", + "type": "string" +}
- Changed
well_list_cash_scope1 field changed- added
Output schema / properties / next_stepAdded value: +{ + "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.", + "type": "string" +}
- Added
well_render_cost_structure - Changed
well_sum_transactions8 fields changed- changed
Input schema / properties / axes / items / enumPrevious value: -[ - "month", - "currency", - "category" -]New value: +[ + "month", + "currency", + "category", + "ledger_account", + "category_label", + "transaction_type" +] - added
Input schema / properties / axes / maxItemsAdded value: +6 - added
Output schema / properties / rows / items / properties / category_labelAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] +} - added
Output schema / properties / rows / items / properties / ledger_accountAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] +} - added
Output schema / properties / rows / items / properties / transaction_typeAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] +} - changed
Output schema / properties / rows / items / requiredPrevious value: -[ - "month", - "currency", - "category_key", - "sum_negative", - "sum_positive", - "count_negative", - "count_positive" -]New value: +[ + "month", + "currency", + "category_key", + "ledger_account", + "category_label", + "transaction_type", + "sum_negative", + "sum_positive", + "count_negative", + "count_positive" +] - added
Output schema / properties / rows_truncatedAdded value: +{ + "type": "boolean" +} - changed
Output schema / requiredPrevious value: -[ - "rows", - "window", - "excluded_malformed", - "excluded_zero_leg", - "excluded_multi_leg", - "excluded_no_owned_leg", - "partial", - "success" -]New value: +[ + "rows", + "window", + "excluded_malformed", + "excluded_zero_leg", + "excluded_multi_leg", + "excluded_no_owned_leg", + "partial", + "rows_truncated", + "success" +]
- Changed
well_switch_workspace3 fields changed- added
Input schema / properties / counted_account_typesAdded value: +{ + "description": "The account types the user ticked as cash, stored as this session's selected_cash_scope and scoped to the workspace this call is dispatched to. Copy each account_type off the group well_list_cash_scope returned; never send a label. An EMPTY list is a real answer — it records that the user counts nothing as cash, which ends the run rather than reporting a zero — so send it when they clicked Continue with nothing ticked, and omit the field entirely when they have not answered. The session holds ONE set, so this REPLACES the previous one, and like the exemptions it survives a change of months: a credit card is or is not cash for the business whatever window is read next.", + "items": { + "enum": [ + "deposit", + "credit", + "loan", + "investment", + "payroll", + "other" + ], + "type": "string" + }, + "type": "array" +} - changed
Output schema / properties / changed / items / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "exemptions", - "recurring_contexts", - "next_step", - "connectors_ack", - "bank_ack", - "categorize_ack", - "assign_ack", - "deploy_ack", - "company_pick", - "accounting_settings_ack", - "retarget_ack", - "invite_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "recurring_contexts", + "cash_scope", + "next_step", + "connectors_ack", + "bank_ack", + "categorize_ack", + "assign_ack", + "deploy_ack", + "company_pick", + "accounting_settings_ack", + "retarget_ack", + "invite_ack" +] - added
Output schema / properties / selected_cash_scopeAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +}
- Changed
well_wait_for_selection4 fields changed- changed
Input schema / properties / kind / descriptionPrevious value: -"Which card click to wait for: \"workspace\" (the workspace picker's Use), \"periods\" (the month picker's Validate), \"counterparties\" (the missing-invoices card's Continue), \"exemptions\" (the burn-exemption card's Continue), \"recurring_contexts\" (the recurring-contexts card's Continue), \"connect_ack\" / \"bank_ack\" (the connect card's Continue), \"categorize_ack\" (the categorize card's Continue or Keep for later), \"assign_ack\" (the owner-assignment card's Continue), \"deploy_ack\" (the collect-agents card's Deploy, Continue or Keep for later), \"company_pick\" (the company-candidates card's Use this company or Keep for later), \"invite_ack\" (the invite-members card's Send or Keep for later), \"retarget_ack\" (the connector-retarget card's Confirm or Keep for later), \"next_step\" (a row click on the next-steps card)."New value: +"Which card click to wait for: \"workspace\" (the workspace picker's Use), \"periods\" (the month picker's Validate), \"counterparties\" (the missing-invoices card's Continue), \"exemptions\" (the burn-exemption card's Continue), \"recurring_contexts\" (the recurring-contexts card's Continue), \"cash_scope\" (the cash-scope card's Continue), \"accounting_settings_ack\" (the accounting-settings card's Confirm), \"connect_ack\" / \"bank_ack\" (the connect card's Continue), \"categorize_ack\" (the categorize card's Continue or Keep for later), \"assign_ack\" (the owner-assignment card's Continue), \"deploy_ack\" (the collect-agents card's Deploy, Continue or Keep for later), \"company_pick\" (the company-candidates card's Use this company or Keep for later), \"invite_ack\" (the invite-members card's Send or Keep for later), \"retarget_ack\" (the connector-retarget card's Confirm or Keep for later), \"next_step\" (a row click on the next-steps card)." - changed
Input schema / properties / kind / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "exemptions", - "recurring_contexts", - "connect_ack", - "bank_ack", - "categorize_ack", - "deploy_ack", - "assign_ack", - "accounting_settings_ack", - "next_step", - "company_pick", - "retarget_ack", - "invite_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "recurring_contexts", + "cash_scope", + "connect_ack", + "bank_ack", + "categorize_ack", + "deploy_ack", + "assign_ack", + "accounting_settings_ack", + "next_step", + "company_pick", + "retarget_ack", + "invite_ack" +] - changed
Output schema / properties / kind / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "exemptions", - "recurring_contexts", - "connect_ack", - "bank_ack", - "categorize_ack", - "deploy_ack", - "assign_ack", - "accounting_settings_ack", - "next_step", - "company_pick", - "retarget_ack", - "invite_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "recurring_contexts", + "cash_scope", + "connect_ack", + "bank_ack", + "categorize_ack", + "deploy_ack", + "assign_ack", + "accounting_settings_ack", + "next_step", + "company_pick", + "retarget_ack", + "invite_ack" +] - added
Output schema / properties / selection / properties / counted_account_typesAdded value: +{ + "description": "The account types the user counts as cash on kind \"cash_scope\". An EMPTY array is the answer \"nothing counts as cash\", not an absent one — it ends the run rather than reporting a zero total.", + "items": { + "type": "string" + }, + "type": "array" +}
18 tool updates
- Changed
well_add_contact_channel9 fields changed- added
Input schema / properties / value / properties / address_line1 / maxLengthAdded value: +255 - added
Input schema / properties / value / properties / address_line2 / maxLengthAdded value: +255 - added
Input schema / properties / value / properties / city / maxLengthAdded value: +255 - added
Input schema / properties / value / properties / country / maxLengthAdded value: +2 - added
Input schema / properties / value / properties / e164_number / maxLengthAdded value: +255 - added
Input schema / properties / value / properties / email / maxLengthAdded value: +320 - added
Input schema / properties / value / properties / postal_code / maxLengthAdded value: +255 - added
Input schema / properties / value / properties / region / maxLengthAdded value: +255 - added
Input schema / properties / value / properties / url / maxLengthAdded value: +255
- Added
well_claim_statement_draft - Changed
well_create_company1 field changed- added
Input schema / properties / description / maxLengthAdded value: +250
- Changed
well_create_invoice_from_data4 fields changed- added
Input schema / properties / issuer / properties / name / maxLengthAdded value: +255 - added
Input schema / properties / line_items / items / properties / name / maxLengthAdded value: +255 - added
Input schema / properties / receiver / properties / name / maxLengthAdded value: +255 - added
Input schema / properties / reference_number / maxLengthAdded value: +100
- Changed
well_create_person2 fields changed- added
Input schema / properties / job_title / maxLengthAdded value: +100 - changed
Input schema / properties / last_name / anyOfPrevious value: -[ - { - "maxLength": 100, - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "maxLength": 255, + "type": "string" + }, + { + "type": "null" + } +]
- Removed
well_get_cash_forecast - Changed
well_get_connector_coverage5 fields changed- changed
Input schema / properties / kind / descriptionPrevious value: -"Scope the card server-side. Three financial domains: \"bank\" (every bank and neobank, including the long tail of open-banking institutions, plus the platforms that hold an account like Qonto and Pennylane — never a payroll or billing tool that merely reports transactions), \"accounting\", and \"invoicing\". Plus \"upload_surface\": the places invoices ARRIVE — mailboxes, messaging apps, file drives — which is a set of display categories rather than a financial domain, resolved by the server. Use this for a connect-a-bank or a connect-where-invoices-arrive step instead of filtering the default view yourself. Omit for every connectable connector."New value: +"Scope the card server-side. Three financial domains: \"bank\" (every bank and neobank, including the long tail of open-banking institutions, plus the platforms that hold an account like Qonto and Pennylane — never a payroll or billing tool that merely reports transactions), \"accounting\", and \"invoicing\". Plus two scopes the server resolves from display categories rather than from a financial domain: \"upload_surface\", the places invoices ARRIVE (mailboxes, messaging apps, file drives), and \"storage\", the drives Well FILES INTO (Google Drive, Dropbox, OneDrive). The last two are opposite directions on the same drives, so a workspace can hold both connections and each is offered on its own card. Use this for a connect-a-bank, a connect-where-invoices-arrive or a connect-where-Well-files step instead of filtering the default view yourself. Omit for every connectable connector." - changed
Input schema / properties / kind / enumPrevious value: -[ - "bank", - "accounting", - "invoicing", - "upload_surface" -]New value: +[ + "bank", + "accounting", + "invoicing", + "upload_surface", + "storage" +] - changed
Output schema / properties / connectors / items / properties / connection_status / anyOfPrevious value: -[ - { - "enum": [ - "enabled", - "processing", - "error", - "need_reconnect", - "to_configure", - "suspended", - "disabled" - ], - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "enum": [ + "enabled", + "processing", + "error", + "need_reconnect", + "to_configure", + "disabled" + ], + "type": "string" + }, + { + "type": "null" + } +] - added
Output schema / properties / row_countAdded value: +{ + "description": "How many rows this result puts ON THE CARD, prepended rows included. `0` on a `picked_vendors` scope means the pick has no connector behind it that can bring an invoice in: the card carries nothing to tick, so say so in half a sentence and move on. Not a paging cursor — `page_count` is.", + "type": "number" +} - changed
Output schema / properties / scope / enumPrevious value: -[ - "catalog", - "bank", - "accounting", - "invoicing", - "upload_surface", - "picked_vendors" -]New value: +[ + "catalog", + "bank", + "accounting", + "invoicing", + "upload_surface", + "storage", + "picked_vendors" +]
- Added
well_get_customer_einvoicing_details - Changed
well_get_session_digest1 field changed- changed
Output schema / properties / situation / properties / connectors / anyOfPrevious value: -[ - { - "additionalProperties": false, - "properties": { - "connected": { - "description": "The tools this workspace has connected, read from its own connection rows. A row still waiting on its handshake, and one torn down, are not carried.", - "items": { - "additionalProperties": false, - "properties": { - "connection_status": { - "anyOf": [ - { - "enum": [ - "enabled", - "processing", - "error", - "need_reconnect", - "to_configure", - "suspended", - "disabled" - ], - "type": "string" - }, - { - "type": "null" - } - ] - }, - "is_connected": { - "type": "boolean" - }, - "kinds": { - "description": "The data domains this tool serves (bank, accounting, invoicing), as the connect card classifies it.", - "items": { - "enum": [ - "bank", - "accounting", - "invoicing" - ], - "type": "string" - }, - "type": "array" - }, - "last_successful_sync_at": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "name": { - "type": "string" - } - }, - "required": [ - "name", - "kinds", - "is_connected", - "connection_status", - "last_successful_sync_at" - ], - "type": "object" - }, - "type": "array" - }, - "connected_count": { - "description": "Connected tools, which is the length of `connected`.", - "type": "number" - } - }, - "required": [ - "connected", - "connected_count" - ], - "type": "object" - }, - { - "type": "null" - } -]New value: +[ + { + "additionalProperties": false, + "properties": { + "connected": { + "description": "The tools this workspace has connected, read from its own connection rows. A row still waiting on its handshake, and one torn down, are not carried.", + "items": { + "additionalProperties": false, + "properties": { + "connection_status": { + "anyOf": [ + { + "enum": [ + "enabled", + "processing", + "error", + "need_reconnect", + "to_configure", + "disabled" + ], + "type": "string" + }, + { + "type": "null" + } + ] + }, + "is_connected": { + "type": "boolean" + }, + "kinds": { + "description": "The data domains this tool serves (bank, accounting, invoicing), as the connect card classifies it.", + "items": { + "enum": [ + "bank", + "accounting", + "invoicing" + ], + "type": "string" + }, + "type": "array" + }, + "last_successful_sync_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + } + }, + "required": [ + "name", + "kinds", + "is_connected", + "connection_status", + "last_successful_sync_at" + ], + "type": "object" + }, + "type": "array" + }, + "connected_count": { + "description": "Connected tools, which is the length of `connected`.", + "type": "number" + } + }, + "required": [ + "connected", + "connected_count" + ], + "type": "object" + }, + { + "type": "null" + } +]
- Changed
well_get_statement_import_result2 fields changed- changed
Output schema / properties / graph / properties / graph / properties / edges / items / properties / kind / enumPrevious value: -[ - "company_person", - "invoice_issuer", - "invoice_receiver", - "invoice_transaction", - "invoice_document", - "transaction_document", - "account_transaction", - "journal_entry_transaction", - "journal_entry_ledger_account", - "business_relation", - "workspace_connector_link", - "invoice_payment_means", - "transaction_payment_means", - "task_invoice", - "journal_journal_entry", - "provider_for", - "workspace_membership", - "same_entity" -]New value: +[ + "company_person", + "invoice_issuer", + "invoice_receiver", + "invoice_transaction", + "invoice_document", + "transaction_document", + "account_transaction", + "journal_entry_transaction", + "journal_entry_ledger_account", + "business_relation", + "workspace_connector_link", + "invoice_payment_means", + "transaction_payment_means", + "task_invoice", + "journal_journal_entry", + "provider_for", + "workspace_membership", + "company_note", + "person_note", + "note_note", + "same_entity" +] - changed
Output schema / properties / graph / properties / graph / properties / nodes / items / properties / type / enumPrevious value: -[ - "company", - "account", - "person", - "invoice", - "transaction", - "document", - "ledger_account", - "journal_entry", - "workspace_connector", - "payment_means", - "card", - "check", - "task", - "media", - "journal", - "blueprint_run", - "chat_conversation", - "connector", - "membership", - "memory", - "custom_column", - "field_rule", - "provider" -]New value: +[ + "company", + "account", + "person", + "invoice", + "transaction", + "document", + "ledger_account", + "journal_entry", + "workspace_connector", + "payment_means", + "card", + "check", + "task", + "media", + "journal", + "blueprint_run", + "chat_conversation", + "connector", + "membership", + "memory", + "custom_column", + "field_rule", + "provider", + "note" +]
- Changed
well_get_worklist_status3 fields changed- added
Input schema / properties / periodsAdded value: +{ + "description": "`counterparties_to_categorize` only: the calendar months to check, the same list the card takes. Every month must have ended.", + "items": { + "additionalProperties": false, + "properties": { + "calendar_month": { + "description": "Calendar month, 1 = January … 12 = December.", + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "calendar_year": { + "description": "Calendar year, e.g. 2026.", + "maximum": 2100, + "minimum": 2000, + "type": "integer" + } + }, + "required": [ + "calendar_year", + "calendar_month" + ], + "type": "object" + }, + "maxItems": 12, + "minItems": 1, + "type": "array" +} - changed
Input schema / properties / worklist / enumPrevious value: -[ - "accounts_needing_company", - "uncategorized_window", - "unposted_transactions" -]New value: +[ + "accounts_needing_company", + "uncategorized_window", + "unposted_transactions", + "invoice_sources_for_pick", + "counterparties_to_categorize" +] - added
Output schema / properties / countAdded value: +{ + "description": "How many rows the gate still holds. Carried only by the kinds whose read counts the whole set (`invoice_sources_for_pick`, and `counterparties_to_categorize` where it equals the card's `uncategorized_count`); absent on the kinds answered one row at a time, and absent whenever `success` is false.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" +}
- Changed
well_list_connectors5 fields changed- changed
Input schema / properties / kind / descriptionPrevious value: -"Scope the card server-side. Three financial domains: \"bank\" (every bank and neobank, including the long tail of open-banking institutions, plus the platforms that hold an account like Qonto and Pennylane — never a payroll or billing tool that merely reports transactions), \"accounting\", and \"invoicing\". Plus \"upload_surface\": the places invoices ARRIVE — mailboxes, messaging apps, file drives — which is a set of display categories rather than a financial domain, resolved by the server. Use this for a connect-a-bank or a connect-where-invoices-arrive step instead of filtering the default view yourself. Omit for every connectable connector."New value: +"Scope the card server-side. Three financial domains: \"bank\" (every bank and neobank, including the long tail of open-banking institutions, plus the platforms that hold an account like Qonto and Pennylane — never a payroll or billing tool that merely reports transactions), \"accounting\", and \"invoicing\". Plus two scopes the server resolves from display categories rather than from a financial domain: \"upload_surface\", the places invoices ARRIVE (mailboxes, messaging apps, file drives), and \"storage\", the drives Well FILES INTO (Google Drive, Dropbox, OneDrive). The last two are opposite directions on the same drives, so a workspace can hold both connections and each is offered on its own card. Use this for a connect-a-bank, a connect-where-invoices-arrive or a connect-where-Well-files step instead of filtering the default view yourself. Omit for every connectable connector." - changed
Input schema / properties / kind / enumPrevious value: -[ - "bank", - "accounting", - "invoicing", - "upload_surface" -]New value: +[ + "bank", + "accounting", + "invoicing", + "upload_surface", + "storage" +] - changed
Output schema / properties / connectors / items / properties / connection_status / anyOfPrevious value: -[ - { - "enum": [ - "enabled", - "processing", - "error", - "need_reconnect", - "to_configure", - "suspended", - "disabled" - ], - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "enum": [ + "enabled", + "processing", + "error", + "need_reconnect", + "to_configure", + "disabled" + ], + "type": "string" + }, + { + "type": "null" + } +] - added
Output schema / properties / row_countAdded value: +{ + "description": "How many rows this result puts ON THE CARD, prepended rows included. `0` on a `picked_vendors` scope means the pick has no connector behind it that can bring an invoice in: the card carries nothing to tick, so say so in half a sentence and move on. Not a paging cursor — `page_count` is.", + "type": "number" +} - changed
Output schema / properties / scope / enumPrevious value: -[ - "catalog", - "bank", - "accounting", - "invoicing", - "upload_surface", - "picked_vendors" -]New value: +[ + "catalog", + "bank", + "accounting", + "invoicing", + "upload_surface", + "storage", + "picked_vendors" +]
- Added
well_render_cash_forecast - Changed
well_render_runway2 fields changed- added
Input schema / properties / windowAdded value: +{ + "additionalProperties": false, + "description": "Inclusive start and EXCLUSIVE end of the months the burn averaged, YYYY-MM-DD.", + "properties": { + "from": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + "to": { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + } + }, + "required": [ + "from", + "to" + ], + "type": "object" +} - added
Output schema / properties / windowAdded value: +{ + "additionalProperties": false, + "properties": { + "from": { + "type": "string" + }, + "to": { + "type": "string" + } + }, + "required": [ + "from", + "to" + ], + "type": "object" +}
- Changed
well_update_company7 fields changed- added
Input schema / properties / decisionAdded value: +{ + "description": "How the user arrived at `category_ids`. Omit it: a request the user typed is an explicit choice, and accepted_suggestion belongs to the categorization card. See the description.", + "enum": [ + "accepted_suggestion", + "explicit" + ], + "type": "string" +} - changed
Input schema / properties / description / anyOfPrevious value: -[ - { - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "maxLength": 250, + "type": "string" + }, + { + "type": "null" + } +] - changed
Input schema / properties / domain / anyOfPrevious value: -[ - { - "maxLength": 255, - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "maxLength": 500, + "type": "string" + }, + { + "type": "null" + } +] - changed
Input schema / properties / registered_value / anyOfPrevious value: -[ - { - "maxLength": 50, - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "maxLength": 100, + "type": "string" + }, + { + "type": "null" + } +] - changed
Input schema / properties / tax_id_value / anyOfPrevious value: -[ - { - "maxLength": 50, - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "maxLength": 255, + "type": "string" + }, + { + "type": "null" + } +] - changed
Input schema / properties / trade_name / anyOfPrevious value: -[ - { - "maxLength": 255, - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "maxLength": 100, + "type": "string" + }, + { + "type": "null" + } +] - added
Output schema / properties / decisionAdded value: +{ + "enum": [ + "accepted_suggestion", + "explicit" + ], + "type": "string" +}
- Changed
well_update_invoice2 fields changed- changed
Input schema / properties / description / anyOfPrevious value: -[ - { - "maxLength": 2000, - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "maxLength": 255, + "type": "string" + }, + { + "type": "null" + } +] - changed
Input schema / properties / terms / anyOfPrevious value: -[ - { - "maxLength": 1000, - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "maxLength": 300, + "type": "string" + }, + { + "type": "null" + } +]
- Changed
well_update_person1 field changed- changed
Input schema / properties / job_title / anyOfPrevious value: -[ - { - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "maxLength": 100, + "type": "string" + }, + { + "type": "null" + } +]
- Changed
well_upload_document1 field changed- removed
Input schema / properties / filename / maxLengthRemoved value: -255
14 tool updates
- Changed
well_create_company1 field changed- removed
Input schema / properties / description / maxLengthRemoved value: -1000
- Changed
well_create_company_candidate1 field changed- changed
Output schema / properties / candidate / anyOfPrevious value: -[ - { - "additionalProperties": false, - "properties": { - "candidate_id": { - "type": "string" - }, - "company_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Null on a fresh candidate; a company row is resolved only once the workspace is created." - }, - "confidence_score": { - "type": "number" - }, - "registered_name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "role": { - "anyOf": [ - { - "enum": [ - "primary", - "sibling" - ], - "type": "string" - }, - { - "type": "null" - } - ] - }, - "state": { - "enum": [ - "pending", - "confirmed", - "dismissed", - "snoozed" - ], - "type": "string" - }, - "trade_name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "candidate_id", - "company_id", - "registered_name", - "trade_name", - "role", - "state", - "confidence_score" - ], - "type": "object" - }, - { - "type": "null" - } -]New value: +[ + { + "additionalProperties": false, + "properties": { + "candidate_id": { + "type": "string" + }, + "company_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Null on a fresh candidate; a company row is resolved only once the workspace is created." + }, + "confidence_score": { + "type": "number" + }, + "confirmable": { + "type": "boolean" + }, + "confirmable_reason": { + "anyOf": [ + { + "enum": [ + "not_open", + "not_surfaceable", + "not_primary", + "already_confirmed", + "not_grounded" + ], + "type": "string" + }, + { + "type": "null" + } + ] + }, + "registered_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "role": { + "anyOf": [ + { + "enum": [ + "primary", + "sibling" + ], + "type": "string" + }, + { + "type": "null" + } + ] + }, + "state": { + "enum": [ + "pending", + "confirmed", + "dismissed", + "snoozed" + ], + "type": "string" + }, + "trade_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "candidate_id", + "company_id", + "registered_name", + "trade_name", + "role", + "state", + "confidence_score", + "confirmable", + "confirmable_reason" + ], + "type": "object" + }, + { + "type": "null" + } +]
- Changed
well_get_connector_coverage8 fields changed- changed
Input schema / properties / from_selection / descriptionPrevious value: -"Scope the card to the connectors behind the counterparties the user picked on the missing-invoices card this session, each installable one pre-checked. Use it for the connect step that FOLLOWS a vendor pick, where the user has already chosen and the card must offer only what they chose. Cannot be combined with `q` or `kind` — those name a catalog to browse, and this names a set already decided. Returns an empty list when the session holds no pick for this workspace, or when no picked counterparty matched a connector."New value: +"Scope the card to the connectors behind the counterparties the user picked on the missing-invoices card this session, each installable one pre-checked. Use it for the connect step that FOLLOWS a vendor pick. It returns those vendors' connectors and NOTHING else: a picked connector that brings no invoices in is dropped, and no accounting or invoicing tool the user did not pick is offered here — that offer is its own step, scoped with `kind`. Cannot be combined with `q` or `kind` — those name a catalog to browse, and this names a set already decided. Returns an empty list when the session holds no pick for this workspace, or when no picked counterparty matched a connector." - changed
Input schema / properties / kind / descriptionPrevious value: -"Scope the catalog to one financial domain: \"bank\" (every bank and neobank, including the long tail of open-banking institutions, plus the platforms that hold an account like Qonto and Pennylane — never a payroll or billing tool that merely reports transactions), \"accounting\", or \"invoicing\". Use this for a connect-a-bank or connect-an-accounting-tool step instead of filtering the default view yourself. Omit for every connectable connector."New value: +"Scope the card server-side. Three financial domains: \"bank\" (every bank and neobank, including the long tail of open-banking institutions, plus the platforms that hold an account like Qonto and Pennylane — never a payroll or billing tool that merely reports transactions), \"accounting\", and \"invoicing\". Plus \"upload_surface\": the places invoices ARRIVE — mailboxes, messaging apps, file drives — which is a set of display categories rather than a financial domain, resolved by the server. Use this for a connect-a-bank or a connect-where-invoices-arrive step instead of filtering the default view yourself. Omit for every connectable connector." - changed
Input schema / properties / kind / enumPrevious value: -[ - "bank", - "accounting", - "invoicing" -]New value: +[ + "bank", + "accounting", + "invoicing", + "upload_surface" +] - added
Output schema / properties / connectors / items / properties / invoice_sourceAdded value: +{ + "type": "boolean" +} - added
Output schema / properties / connectors / items / properties / reasonAdded value: +{ + "enum": [ + "catalog", + "picked_vendor" + ], + "type": "string" +} - changed
Output schema / properties / connectors / items / requiredPrevious value: -[ - "service_id", - "name", - "category_id", - "status", - "direction", - "data_domains", - "logo_url", - "popularity_score", - "is_matched", - "is_selected", - "match_score", - "is_connected", - "connection_status", - "workspace_connector_id", - "last_successful_sync_at", - "sync_in_progress", - "is_preselected", - "install_url", - "countries" -]New value: +[ + "service_id", + "name", + "category_id", + "status", + "direction", + "data_domains", + "invoice_source", + "reason", + "logo_url", + "popularity_score", + "is_matched", + "is_selected", + "match_score", + "is_connected", + "connection_status", + "workspace_connector_id", + "last_successful_sync_at", + "sync_in_progress", + "is_preselected", + "install_url", + "countries" +] - added
Output schema / properties / picked_vendors_filteredAdded value: +{ + "description": "How many of the picked vendors' connectors were dropped for bringing no invoices in. Present on the `picked_vendors` scope only. Above zero means the card is SHORTER than the pick: say that those vendors' tools cannot deliver an invoice, rather than letting the gap read as a pick the user never made.", + "type": "number" +} - changed
Output schema / properties / scope / enumPrevious value: -[ - "catalog", - "bank", - "accounting", - "invoicing", - "picked_vendors" -]New value: +[ + "catalog", + "bank", + "accounting", + "invoicing", + "upload_surface", + "picked_vendors" +]
- Changed
well_get_own_company7 fields changed- added
Output schema / properties / candidates / items / properties / business_typeAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The company's legal form (SAS, Inc, GmbH), when known." +} - added
Output schema / properties / candidates / items / properties / confirmableAdded value: +{ + "description": "Whether well_create_company_workspace accepts this candidate now. False for a name-only detected candidate with no groundable identity, or one that has left the surfaced set.", + "type": "boolean" +} - added
Output schema / properties / candidates / items / properties / confirmable_reasonAdded value: +{ + "anyOf": [ + { + "enum": [ + "not_open", + "not_surfaceable", + "not_primary", + "already_confirmed", + "not_grounded" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Why confirmable is false, null when true. not_grounded: no registry or tax identifier to mint from; ask the user to search the registry and pick the verified entry. not_open: no longer the surfaced candidate; re-read the own-company list before acting. Others: not_primary, already_confirmed, not_surfaceable." +} - added
Output schema / properties / candidates / items / properties / countryAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The company's country, for a light detail panel." +} - added
Output schema / properties / candidates / items / properties / domainAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The company's domain, a logo source and a display fallback." +} - added
Output schema / properties / candidates / items / properties / remote_logo_urlAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "A resolved logo url when one is known; null otherwise." +} - changed
Output schema / properties / candidates / items / requiredPrevious value: -[ - "candidate_id", - "company_id", - "registered_name", - "trade_name", - "role", - "state", - "confidence_score" -]New value: +[ + "candidate_id", + "company_id", + "registered_name", + "trade_name", + "role", + "state", + "confidence_score", + "domain", + "remote_logo_url", + "country", + "business_type", + "confirmable", + "confirmable_reason" +]
- Changed
well_list_burn_exemptions1 field changed- changed
Output schema / properties / partial / descriptionPrevious value: -"True when the aggregate behind these figures was cut short. Every amount is then a FLOOR rather than a measurement, and a category's true share can only be larger — so say so before a reader exempts on the strength of one being small."New value: +"True when the sum behind this card measured nothing. `groups` is then empty, so there is no list to choose from: say so and offer to try again."
- Changed
well_list_connectors8 fields changed- changed
Input schema / properties / from_selection / descriptionPrevious value: -"Scope the card to the connectors behind the counterparties the user picked on the missing-invoices card this session, each installable one pre-checked. Use it for the connect step that FOLLOWS a vendor pick, where the user has already chosen and the card must offer only what they chose. Cannot be combined with `q` or `kind` — those name a catalog to browse, and this names a set already decided. Returns an empty list when the session holds no pick for this workspace, or when no picked counterparty matched a connector."New value: +"Scope the card to the connectors behind the counterparties the user picked on the missing-invoices card this session, each installable one pre-checked. Use it for the connect step that FOLLOWS a vendor pick. It returns those vendors' connectors and NOTHING else: a picked connector that brings no invoices in is dropped, and no accounting or invoicing tool the user did not pick is offered here — that offer is its own step, scoped with `kind`. Cannot be combined with `q` or `kind` — those name a catalog to browse, and this names a set already decided. Returns an empty list when the session holds no pick for this workspace, or when no picked counterparty matched a connector." - changed
Input schema / properties / kind / descriptionPrevious value: -"Scope the catalog to one financial domain: \"bank\" (every bank and neobank, including the long tail of open-banking institutions, plus the platforms that hold an account like Qonto and Pennylane — never a payroll or billing tool that merely reports transactions), \"accounting\", or \"invoicing\". Use this for a connect-a-bank or connect-an-accounting-tool step instead of filtering the default view yourself. Omit for every connectable connector."New value: +"Scope the card server-side. Three financial domains: \"bank\" (every bank and neobank, including the long tail of open-banking institutions, plus the platforms that hold an account like Qonto and Pennylane — never a payroll or billing tool that merely reports transactions), \"accounting\", and \"invoicing\". Plus \"upload_surface\": the places invoices ARRIVE — mailboxes, messaging apps, file drives — which is a set of display categories rather than a financial domain, resolved by the server. Use this for a connect-a-bank or a connect-where-invoices-arrive step instead of filtering the default view yourself. Omit for every connectable connector." - changed
Input schema / properties / kind / enumPrevious value: -[ - "bank", - "accounting", - "invoicing" -]New value: +[ + "bank", + "accounting", + "invoicing", + "upload_surface" +] - added
Output schema / properties / connectors / items / properties / invoice_sourceAdded value: +{ + "type": "boolean" +} - added
Output schema / properties / connectors / items / properties / reasonAdded value: +{ + "enum": [ + "catalog", + "picked_vendor" + ], + "type": "string" +} - changed
Output schema / properties / connectors / items / requiredPrevious value: -[ - "service_id", - "name", - "category_id", - "status", - "direction", - "data_domains", - "logo_url", - "popularity_score", - "is_matched", - "is_selected", - "match_score", - "is_connected", - "connection_status", - "workspace_connector_id", - "last_successful_sync_at", - "sync_in_progress", - "is_preselected", - "install_url", - "countries" -]New value: +[ + "service_id", + "name", + "category_id", + "status", + "direction", + "data_domains", + "invoice_source", + "reason", + "logo_url", + "popularity_score", + "is_matched", + "is_selected", + "match_score", + "is_connected", + "connection_status", + "workspace_connector_id", + "last_successful_sync_at", + "sync_in_progress", + "is_preselected", + "install_url", + "countries" +] - added
Output schema / properties / picked_vendors_filteredAdded value: +{ + "description": "How many of the picked vendors' connectors were dropped for bringing no invoices in. Present on the `picked_vendors` scope only. Above zero means the card is SHORTER than the pick: say that those vendors' tools cannot deliver an invoice, rather than letting the gap read as a pick the user never made.", + "type": "number" +} - changed
Output schema / properties / scope / enumPrevious value: -[ - "catalog", - "bank", - "accounting", - "invoicing", - "picked_vendors" -]New value: +[ + "catalog", + "bank", + "accounting", + "invoicing", + "upload_surface", + "picked_vendors" +]
- Changed
well_list_member_candidates1 field changed- added
Output schema / properties / next_stepAdded value: +{ + "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.", + "type": "string" +}
- Changed
well_list_periods2 fields changed- changed
Output schema / properties / periods / items / properties / categorized_unposted_transactions / descriptionPrevious value: -"Categorized transactions not yet posted to the ledger — part of the 'review and book' close errand. Dated on the books, and likewise ABSENT rather than 0 on a \"analysis\" call."New value: +"Categorized transactions not yet posted to the ledger — part of the 'review and book' close errand. Dated on the books, and likewise ABSENT rather than 0 on a \"analysis\" call and on a month the coverage read did not cover." - changed
Output schema / properties / periods / items / properties / uncategorized_transactions / descriptionPrevious value: -"Transactions in the month not yet categorized — the 'help categorize' close errand. Dated on the books, so it is ABSENT on a \"analysis\" call rather than 0: that purpose counts on execution and never measures this errand."New value: +"Transactions in the month not yet categorized — the 'help categorize' close errand. Dated on the books, so it is ABSENT on a \"analysis\" call rather than 0: that purpose counts on execution and never measures this errand. Absent too on a month the coverage read did not cover. An absent count is never \"nothing left to categorize\"."
- Added
well_search_context - Changed
well_show_company_candidates7 fields changed- added
Output schema / properties / candidates / items / properties / business_typeAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The company's legal form (SAS, Inc, GmbH), when known." +} - added
Output schema / properties / candidates / items / properties / confirmableAdded value: +{ + "description": "Whether well_create_company_workspace accepts this candidate now. False for a name-only detected candidate with no groundable identity, or one that has left the surfaced set.", + "type": "boolean" +} - added
Output schema / properties / candidates / items / properties / confirmable_reasonAdded value: +{ + "anyOf": [ + { + "enum": [ + "not_open", + "not_surfaceable", + "not_primary", + "already_confirmed", + "not_grounded" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Why confirmable is false, null when true. not_grounded: no registry or tax identifier to mint from; ask the user to search the registry and pick the verified entry. not_open: no longer the surfaced candidate; re-read the own-company list before acting. Others: not_primary, already_confirmed, not_surfaceable." +} - added
Output schema / properties / candidates / items / properties / countryAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The company's country, for a light detail panel." +} - added
Output schema / properties / candidates / items / properties / domainAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The company's domain, a logo source and a display fallback." +} - added
Output schema / properties / candidates / items / properties / remote_logo_urlAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "A resolved logo url when one is known; null otherwise." +} - changed
Output schema / properties / candidates / items / requiredPrevious value: -[ - "candidate_id", - "company_id", - "registered_name", - "trade_name", - "role", - "state", - "confidence_score" -]New value: +[ + "candidate_id", + "company_id", + "registered_name", + "trade_name", + "role", + "state", + "confidence_score", + "domain", + "remote_logo_url", + "country", + "business_type", + "confirmable", + "confirmable_reason" +]
- Changed
well_switch_workspace5 fields changed- changed
Input schema / properties / ack / descriptionPrevious value: -"Acknowledge a flow step: \"connectors\" for the connect-tools step, \"bank\" for the bank step, \"categorize\" for the counterparty-categorization step, \"assign\" for the assign-owners step, \"deploy\" for the collect-agents step, \"retarget\" for the connector-retarget (data-migration) card. \"retarget\" is the retarget card's own ack: the card sends it on its Confirm with ack_outcome \"done\", or on its Keep for later with \"keep_for_later\", so you do not send it yourself. The acknowledgement is scoped to the workspace this call is dispatched to, and a switch to another workspace clears it. An acknowledgement sent for a workspace this connection has switched away from is refused, and the pinned workspace's own acknowledgement of that step stands."New value: +"Acknowledge a flow step: \"connectors\" for the connect-tools step, \"bank\" for the bank step, \"categorize\" for the counterparty-categorization step, \"assign\" for the assign-owners step, \"deploy\" for the collect-agents step, \"retarget\" for the connector-retarget (data-migration) card, \"company_pick\" for the company-candidates card, \"invite\" for the invite-members card. \"retarget\" is the retarget card's own ack: the card sends it on its Confirm with ack_outcome \"done\", or on its Keep for later with \"keep_for_later\", so you do not send it yourself. The acknowledgement is scoped to the workspace this call is dispatched to, and a switch to another workspace clears it. An acknowledgement sent for a workspace this connection has switched away from is refused, and the pinned workspace's own acknowledgement of that step stands." - changed
Input schema / properties / ack / enumPrevious value: -[ - "connectors", - "bank", - "categorize", - "assign", - "deploy", - "company_pick", - "accounting_settings", - "retarget" -]New value: +[ + "connectors", + "bank", + "categorize", + "assign", + "deploy", + "company_pick", + "accounting_settings", + "retarget", + "invite" +] - changed
Input schema / properties / ack_outcome / descriptionPrevious value: -"What the click said about the step: \"done\" when the user carried it out, \"keep_for_later\" when they set it aside on purpose. REQUIRED with ack \"categorize\" and \"deploy\" and \"retarget\", whose cards end on two buttons that mean different things, and refused with every other step, whose card confirms and nothing else."New value: +"What the click said about the step: \"done\" when the user carried it out, \"keep_for_later\" when they set it aside on purpose. REQUIRED with ack \"categorize\" and \"deploy\" and \"retarget\" and \"company_pick\" and \"invite\", whose cards end on two buttons that mean different things, and refused with every other step, whose card confirms and nothing else." - changed
Output schema / properties / acknowledged / enumPrevious value: -[ - "connectors", - "bank", - "categorize", - "assign", - "deploy", - "company_pick", - "accounting_settings", - "retarget" -]New value: +[ + "connectors", + "bank", + "categorize", + "assign", + "deploy", + "company_pick", + "accounting_settings", + "retarget", + "invite" +] - changed
Output schema / properties / changed / items / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "exemptions", - "recurring_contexts", - "next_step", - "connectors_ack", - "bank_ack", - "categorize_ack", - "assign_ack", - "deploy_ack", - "company_pick", - "accounting_settings_ack", - "retarget_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "recurring_contexts", + "next_step", + "connectors_ack", + "bank_ack", + "categorize_ack", + "assign_ack", + "deploy_ack", + "company_pick", + "accounting_settings_ack", + "retarget_ack", + "invite_ack" +]
- Changed
well_update_company1 field changed- changed
Input schema / properties / description / anyOfPrevious value: -[ - { - "maxLength": 1000, - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "type": "string" + }, + { + "type": "null" + } +]
- Added
well_upload_document - Changed
well_wait_for_selection3 fields changed- changed
Input schema / properties / kind / descriptionPrevious value: -"Which card click to wait for: \"workspace\" (the workspace picker's Use), \"periods\" (the month picker's Validate), \"counterparties\" (the missing-invoices card's Continue), \"exemptions\" (the burn-exemption card's Continue), \"recurring_contexts\" (the recurring-contexts card's Continue), \"connect_ack\" / \"bank_ack\" (the connect card's Continue), \"categorize_ack\" (the categorize card's Continue or Keep for later), \"assign_ack\" (the owner-assignment card's Continue), \"deploy_ack\" (the collect-agents card's Deploy, Continue or Keep for later), \"company_pick\" (the company-candidates card's Use this company), \"retarget_ack\" (the connector-retarget card's Confirm or Keep for later), \"next_step\" (a row click on the next-steps card)."New value: +"Which card click to wait for: \"workspace\" (the workspace picker's Use), \"periods\" (the month picker's Validate), \"counterparties\" (the missing-invoices card's Continue), \"exemptions\" (the burn-exemption card's Continue), \"recurring_contexts\" (the recurring-contexts card's Continue), \"connect_ack\" / \"bank_ack\" (the connect card's Continue), \"categorize_ack\" (the categorize card's Continue or Keep for later), \"assign_ack\" (the owner-assignment card's Continue), \"deploy_ack\" (the collect-agents card's Deploy, Continue or Keep for later), \"company_pick\" (the company-candidates card's Use this company or Keep for later), \"invite_ack\" (the invite-members card's Send or Keep for later), \"retarget_ack\" (the connector-retarget card's Confirm or Keep for later), \"next_step\" (a row click on the next-steps card)." - changed
Input schema / properties / kind / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "exemptions", - "recurring_contexts", - "connect_ack", - "bank_ack", - "categorize_ack", - "deploy_ack", - "assign_ack", - "accounting_settings_ack", - "next_step", - "company_pick", - "retarget_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "recurring_contexts", + "connect_ack", + "bank_ack", + "categorize_ack", + "deploy_ack", + "assign_ack", + "accounting_settings_ack", + "next_step", + "company_pick", + "retarget_ack", + "invite_ack" +] - changed
Output schema / properties / kind / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "exemptions", - "recurring_contexts", - "connect_ack", - "bank_ack", - "categorize_ack", - "deploy_ack", - "assign_ack", - "accounting_settings_ack", - "next_step", - "company_pick", - "retarget_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "recurring_contexts", + "connect_ack", + "bank_ack", + "categorize_ack", + "deploy_ack", + "assign_ack", + "accounting_settings_ack", + "next_step", + "company_pick", + "retarget_ack", + "invite_ack" +]
29 tool updates
- Added
well_create_company_candidate - Added
well_create_company_workspace - Changed
well_create_person1 field changed- removed
Input schema / properties / job_title / maxLengthRemoved value: -200
- Added
well_get_accounting_settings - Changed
well_get_connector_coverage3 fields changed- added
Input schema / properties / countryAdded value: +{ + "description": "The company's country as an ISO 3166-1 alpha-2 code (e.g. \"FR\"). When set, the connectors that serve that country sort first, then the ones that serve its region, then the rest — the order only, no row is dropped. Use it on a connect-a-bank step so the banks that fit the company appear first; take it from the workspace identity's country. Omit when the country is unknown, and the order is left unchanged.", + "enum": [ + "AD", + "AE", + "AF", + "AG", + "AI", + "AL", + "AM", + "AO", + "AQ", + "AR", + "AS", + "AT", + "AU", + "AW", + "AX", + "AZ", + "BA", + "BB", + "BD", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BL", + "BM", + "BN", + "BO", + "BQ", + "BR", + "BS", + "BT", + "BV", + "BW", + "BY", + "BZ", + "CA", + "CC", + "CD", + "CF", + "CG", + "CH", + "CI", + "CK", + "CL", + "CM", + "CN", + "CO", + "CR", + "CU", + "CV", + "CW", + "CX", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "EH", + "ER", + "ES", + "ET", + "FI", + "FJ", + "FK", + "FM", + "FO", + "FR", + "GA", + "GB", + "GD", + "GE", + "GF", + "GG", + "GH", + "GI", + "GL", + "GM", + "GN", + "GP", + "GQ", + "GR", + "GS", + "GT", + "GU", + "GW", + "GY", + "HK", + "HM", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IM", + "IN", + "IO", + "IQ", + "IR", + "IS", + "IT", + "JE", + "JM", + "JO", + "JP", + "KE", + "KG", + "KH", + "KI", + "KM", + "KN", + "KP", + "KR", + "KW", + "KY", + "KZ", + "LA", + "LB", + "LC", + "LI", + "LK", + "LR", + "LS", + "LT", + "LU", + "LV", + "LY", + "MA", + "MC", + "MD", + "ME", + "MF", + "MG", + "MH", + "MK", + "ML", + "MM", + "MN", + "MO", + "MP", + "MQ", + "MR", + "MS", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NC", + "NE", + "NF", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NU", + "NZ", + "OM", + "PA", + "PE", + "PF", + "PG", + "PH", + "PK", + "PL", + "PM", + "PN", + "PR", + "PS", + "PT", + "PW", + "PY", + "QA", + "RE", + "RO", + "RS", + "RU", + "RW", + "SA", + "SB", + "SC", + "SD", + "SE", + "SG", + "SH", + "SI", + "SJ", + "SK", + "SL", + "SM", + "SN", + "SO", + "SR", + "SS", + "ST", + "SV", + "SX", + "SY", + "SZ", + "TC", + "TD", + "TF", + "TG", + "TH", + "TJ", + "TK", + "TL", + "TM", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "UA", + "UG", + "UM", + "US", + "UY", + "UZ", + "VA", + "VC", + "VE", + "VG", + "VI", + "VN", + "VU", + "WF", + "WS", + "YE", + "YT", + "ZA", + "ZM", + "ZW" + ], + "type": "string" +} - added
Output schema / properties / connectors / items / properties / countriesAdded value: +{ + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ] +} - changed
Output schema / properties / connectors / items / requiredPrevious value: -[ - "service_id", - "name", - "category_id", - "status", - "direction", - "data_domains", - "logo_url", - "popularity_score", - "is_matched", - "is_selected", - "match_score", - "is_connected", - "connection_status", - "workspace_connector_id", - "last_successful_sync_at", - "sync_in_progress", - "is_preselected", - "install_url" -]New value: +[ + "service_id", + "name", + "category_id", + "status", + "direction", + "data_domains", + "logo_url", + "popularity_score", + "is_matched", + "is_selected", + "match_score", + "is_connected", + "connection_status", + "workspace_connector_id", + "last_successful_sync_at", + "sync_in_progress", + "is_preselected", + "install_url", + "countries" +]
- Changed
well_get_own_company2 fields changed- added
Output schema / properties / candidates / items / properties / candidate_idAdded value: +{ + "description": "The candidate's id — pass to well_create_company_workspace to mint the company workspace from it.", + "type": "string" +} - changed
Output schema / properties / candidates / items / requiredPrevious value: -[ - "company_id", - "registered_name", - "trade_name", - "role", - "state", - "confidence_score" -]New value: +[ + "candidate_id", + "company_id", + "registered_name", + "trade_name", + "role", + "state", + "confidence_score" +]
- Changed
well_get_worklist_status2 fields changed- changed
Input schema / properties / fiscal_period / descriptionPrevious value: -"`unposted_transactions` only: the period's fiscal period, 1-12."New value: +"`unposted_transactions` only: the fiscal period, 1-12 for a month; 13 is the adjustment period and resolves to no rows." - changed
Input schema / properties / fiscal_period / maximumPrevious value: -12New value: +13
- Changed
well_list_connectors3 fields changed- added
Input schema / properties / countryAdded value: +{ + "description": "The company's country as an ISO 3166-1 alpha-2 code (e.g. \"FR\"). When set, the connectors that serve that country sort first, then the ones that serve its region, then the rest — the order only, no row is dropped. Use it on a connect-a-bank step so the banks that fit the company appear first; take it from the workspace identity's country. Omit when the country is unknown, and the order is left unchanged.", + "enum": [ + "AD", + "AE", + "AF", + "AG", + "AI", + "AL", + "AM", + "AO", + "AQ", + "AR", + "AS", + "AT", + "AU", + "AW", + "AX", + "AZ", + "BA", + "BB", + "BD", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BL", + "BM", + "BN", + "BO", + "BQ", + "BR", + "BS", + "BT", + "BV", + "BW", + "BY", + "BZ", + "CA", + "CC", + "CD", + "CF", + "CG", + "CH", + "CI", + "CK", + "CL", + "CM", + "CN", + "CO", + "CR", + "CU", + "CV", + "CW", + "CX", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "EH", + "ER", + "ES", + "ET", + "FI", + "FJ", + "FK", + "FM", + "FO", + "FR", + "GA", + "GB", + "GD", + "GE", + "GF", + "GG", + "GH", + "GI", + "GL", + "GM", + "GN", + "GP", + "GQ", + "GR", + "GS", + "GT", + "GU", + "GW", + "GY", + "HK", + "HM", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IM", + "IN", + "IO", + "IQ", + "IR", + "IS", + "IT", + "JE", + "JM", + "JO", + "JP", + "KE", + "KG", + "KH", + "KI", + "KM", + "KN", + "KP", + "KR", + "KW", + "KY", + "KZ", + "LA", + "LB", + "LC", + "LI", + "LK", + "LR", + "LS", + "LT", + "LU", + "LV", + "LY", + "MA", + "MC", + "MD", + "ME", + "MF", + "MG", + "MH", + "MK", + "ML", + "MM", + "MN", + "MO", + "MP", + "MQ", + "MR", + "MS", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NC", + "NE", + "NF", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NU", + "NZ", + "OM", + "PA", + "PE", + "PF", + "PG", + "PH", + "PK", + "PL", + "PM", + "PN", + "PR", + "PS", + "PT", + "PW", + "PY", + "QA", + "RE", + "RO", + "RS", + "RU", + "RW", + "SA", + "SB", + "SC", + "SD", + "SE", + "SG", + "SH", + "SI", + "SJ", + "SK", + "SL", + "SM", + "SN", + "SO", + "SR", + "SS", + "ST", + "SV", + "SX", + "SY", + "SZ", + "TC", + "TD", + "TF", + "TG", + "TH", + "TJ", + "TK", + "TL", + "TM", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "UA", + "UG", + "UM", + "US", + "UY", + "UZ", + "VA", + "VC", + "VE", + "VG", + "VI", + "VN", + "VU", + "WF", + "WS", + "YE", + "YT", + "ZA", + "ZM", + "ZW" + ], + "type": "string" +} - added
Output schema / properties / connectors / items / properties / countriesAdded value: +{ + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ] +} - changed
Output schema / properties / connectors / items / requiredPrevious value: -[ - "service_id", - "name", - "category_id", - "status", - "direction", - "data_domains", - "logo_url", - "popularity_score", - "is_matched", - "is_selected", - "match_score", - "is_connected", - "connection_status", - "workspace_connector_id", - "last_successful_sync_at", - "sync_in_progress", - "is_preselected", - "install_url" -]New value: +[ + "service_id", + "name", + "category_id", + "status", + "direction", + "data_domains", + "logo_url", + "popularity_score", + "is_matched", + "is_selected", + "match_score", + "is_connected", + "connection_status", + "workspace_connector_id", + "last_successful_sync_at", + "sync_in_progress", + "is_preselected", + "install_url", + "countries" +]
- Changed
well_list_periods1 field changed- added
Input schema / properties / replyAdded value: +{ + "description": "One sentence, IN THE LANGUAGE THE USER IS WRITING IN, that the click sends into the conversation as their own message. Write what the PERSON would say about the months they pick, in their words, not an instruction to yourself. You write it BEFORE they click, so you cannot know what they will choose: put {picked} where the pick belongs and the card replaces it with the names they actually picked, one or several. In English it would read \"Let's work on {picked}.\" Write the same shape in the user's language. {picked} is required: a sentence that names a pick itself names the one you guessed, so the card refuses it and sends its own English instead. At most 160 characters. Omitted sends an English sentence the card builds itself, which is wrong for any reader not writing in English.", + "maxLength": 160, + "minLength": 1, + "type": "string" +}
- Added
well_list_recurring_contexts - Added
well_list_retargetable_connectors - Changed
well_list_workspaces6 fields changed- added
Output schema / properties / workspaces / items / properties / identity / properties / country_default_fiscal_year_start_monthAdded value: +{ + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "The jurisdiction's default fiscal-year-start month for this country, or null when the country has no single confident default (non-null for France only today)." +} - added
Output schema / properties / workspaces / items / properties / identity / properties / fiscal_year_start_month_sourceAdded value: +{ + "anyOf": [ + { + "enum": [ + "derived", + "registry", + "user" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Where fiscal_year_start_month came from: \"registry\" from a company registry, \"derived\" from the country fallback, \"user\" from a human. Null when never set. Tells a confirmed fiscal year from one resting on a default." +} - changed
Output schema / properties / workspaces / items / properties / identity / requiredPrevious value: -[ - "registered_name", - "trade_name", - "registered_value", - "country", - "domain", - "base_currency", - "fiscal_year_start_month" -]New value: +[ + "registered_name", + "trade_name", + "registered_value", + "country", + "domain", + "base_currency", + "fiscal_year_start_month", + "fiscal_year_start_month_source", + "country_default_fiscal_year_start_month" +] - added
Output schema / properties / workspaces / items / properties / lineage_parent_workspace_idAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The workspace_id of the membership this workspace was created under, or null when it has no active lineage. Its parent's own row is the membership whose id this points at." +} - added
Output schema / properties / workspaces / items / properties / own_company_idAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The public id of the company this workspace is anchored to, or null. A row that carries it is a company workspace, the one the close flow runs in; a row without it is a membership workspace." +} - changed
Output schema / properties / workspaces / items / requiredPrevious value: -[ - "workspace_id", - "workspace_name", - "is_primary", - "has_bank_transactions", - "identity" -]New value: +[ + "workspace_id", + "workspace_name", + "is_primary", + "own_company_id", + "lineage_parent_workspace_id", + "has_bank_transactions", + "identity" +]
- Changed
well_render_burn16 fields changed- added
Input schema / properties / excluded / properties / internal_transfers / anyOfAdded value: +[ + { + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } +] - added
Input schema / properties / excluded / properties / internal_transfers / descriptionAdded value: +"The sum's `excluded_multi_leg`. `null` when the sum could not count it, never 0." - removed
Input schema / properties / excluded / properties / internal_transfers / maximumRemoved value: -9007199254740991 - removed
Input schema / properties / excluded / properties / internal_transfers / minimumRemoved value: -0 - removed
Input schema / properties / excluded / properties / internal_transfers / typeRemoved value: -"integer" - added
Input schema / properties / unplaceable_count / anyOfAdded value: +[ + { + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } +] - changed
Input schema / properties / unplaceable_count / descriptionPrevious value: -"Rows with no leg on a known account — neither inside nor outside the transfer rule."New value: +"Rows with no leg on an owned account, neither inside nor outside the transfer rule: the sum's `excluded_no_owned_leg`. `null` when the sum could not count them, never 0." - removed
Input schema / properties / unplaceable_count / maximumRemoved value: -9007199254740991 - removed
Input schema / properties / unplaceable_count / minimumRemoved value: -0 - removed
Input schema / properties / unplaceable_count / typeRemoved value: -"integer" - removed
Output schema / properties / callerRemoved value: -{ - "enum": [ - "first_party", - "third_party" - ], - "type": "string" -} - added
Output schema / properties / excluded / properties / internal_transfers / anyOfAdded value: +[ + { + "type": "number" + }, + { + "type": "null" + } +] - removed
Output schema / properties / excluded / properties / internal_transfers / typeRemoved value: -"number" - added
Output schema / properties / unplaceable_count / anyOfAdded value: +[ + { + "type": "number" + }, + { + "type": "null" + } +] - removed
Output schema / properties / unplaceable_count / typeRemoved value: -"number" - changed
Output schema / requiredPrevious value: -[ - "amount", - "currency", - "window", - "months_in_window", - "months_with_data", - "convention", - "convention_counts", - "transaction_count", - "unplaceable_count", - "excluded", - "computed_by", - "caller", - "success" -]New value: +[ + "amount", + "currency", + "window", + "months_in_window", + "months_with_data", + "convention", + "convention_counts", + "transaction_count", + "unplaceable_count", + "excluded", + "computed_by", + "success" +]
- Changed
well_render_cash_flow_bridge2 fields changed- removed
Output schema / properties / callerRemoved value: -{ - "type": "string" -} - changed
Output schema / requiredPrevious value: -[ - "currency", - "period_start", - "period_end", - "opening", - "inflows", - "outflows", - "unexplained", - "closing", - "reconciles", - "partial", - "steps", - "computed_by", - "caller", - "success" -]New value: +[ + "currency", + "period_start", + "period_end", + "opening", + "inflows", + "outflows", + "unexplained", + "closing", + "reconciles", + "partial", + "steps", + "computed_by", + "success" +]
- Changed
well_render_cash_position2 fields changed- removed
Output schema / properties / callerRemoved value: -{ - "type": "string" -} - changed
Output schema / requiredPrevious value: -[ - "amount", - "currency", - "as_of", - "accounts", - "scope", - "excluded", - "partial", - "computed_by", - "caller", - "success" -]New value: +[ + "amount", + "currency", + "as_of", + "accounts", + "scope", + "excluded", + "partial", + "computed_by", + "success" +]
- Added
well_render_mrr - Changed
well_render_runway2 fields changed- removed
Output schema / properties / callerRemoved value: -{ - "type": "string" -} - changed
Output schema / requiredPrevious value: -[ - "months", - "status", - "cash", - "avg_burn", - "as_of", - "partial", - "computed_by", - "caller", - "success" -]New value: +[ + "months", + "status", + "cash", + "avg_burn", + "as_of", + "partial", + "computed_by", + "success" +]
- Changed
well_resolve_reconciliation_task1 field changed- added
Input schema / properties / selectionAdded value: +{ + "description": "For an unresolved reconciliation review task: the transaction the reviewer picked to settle the invoice. Applies to exactly one task; required on approve, rejected elsewhere.", + "properties": { + "allocated_amount": { + "description": "The invoice-currency amount this settlement covers.", + "exclusiveMinimum": 0, + "type": "number" + }, + "target_id": { + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", + "type": "string" + }, + "target_type": { + "const": "transaction", + "type": "string" + } + }, + "required": [ + "target_type", + "target_id", + "allocated_amount" + ], + "type": "object" +}
- Added
well_retarget_connectors - Added
well_search_company_registry - Added
well_show_accounting_settings - Added
well_show_company_candidates - Added
well_show_retargetable_connectors - Changed
well_show_workspace_picker10 fields changed- added
Input schema / properties / replyAdded value: +{ + "description": "One sentence, IN THE LANGUAGE THE USER IS WRITING IN, that the click sends into the conversation as their own message. Write what the PERSON would say about the workspace they pick, in their words, not an instruction to yourself. You write it BEFORE they click, so you cannot know what they will choose: put {picked} where the pick belongs and the card replaces it with the names they actually picked, one or several. In English it would read \"Let's work on {picked}.\" Write the same shape in the user's language. {picked} is required: a sentence that names a pick itself names the one you guessed, so the card refuses it and sends its own English instead. At most 160 characters. Omitted sends an English sentence the card builds itself, which is wrong for any reader not writing in English.", + "maxLength": 160, + "minLength": 1, + "type": "string" +} - added
Input schema / properties / subtitleAdded value: +{ + "description": "Supporting line under the picker card's heading. At most 240 characters. Omit to keep the default wording; an empty string is rejected rather than rendered blank.", + "maxLength": 240, + "minLength": 1, + "type": "string" +} - added
Input schema / properties / titleAdded value: +{ + "description": "Heading for the picker card, framing the step in its flow (e.g. \"Which company are we closing?\"). At most 120 characters. Omit to keep the default wording; an empty string is rejected rather than rendered as a blank heading.", + "maxLength": 120, + "minLength": 1, + "type": "string" +} - added
Input schema / properties / workspace_idsAdded value: +{ + "description": "Scope the tiles to this subset of the authorized workspaces, e.g. the company workspaces under one membership. Every id must be one this token authorizes; an id outside the grant refuses the call. Omit to draw every authorized workspace.", + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" +} - added
Output schema / properties / workspaces / items / properties / identity / properties / country_default_fiscal_year_start_monthAdded value: +{ + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "The jurisdiction's default fiscal-year-start month for this country, or null when the country has no single confident default (non-null for France only today)." +} - added
Output schema / properties / workspaces / items / properties / identity / properties / fiscal_year_start_month_sourceAdded value: +{ + "anyOf": [ + { + "enum": [ + "derived", + "registry", + "user" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Where fiscal_year_start_month came from: \"registry\" from a company registry, \"derived\" from the country fallback, \"user\" from a human. Null when never set. Tells a confirmed fiscal year from one resting on a default." +} - changed
Output schema / properties / workspaces / items / properties / identity / requiredPrevious value: -[ - "registered_name", - "trade_name", - "registered_value", - "country", - "domain", - "base_currency", - "fiscal_year_start_month" -]New value: +[ + "registered_name", + "trade_name", + "registered_value", + "country", + "domain", + "base_currency", + "fiscal_year_start_month", + "fiscal_year_start_month_source", + "country_default_fiscal_year_start_month" +] - added
Output schema / properties / workspaces / items / properties / lineage_parent_workspace_idAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The workspace_id of the membership this workspace was created under, or null when it has no active lineage. Its parent's own row is the membership whose id this points at." +} - added
Output schema / properties / workspaces / items / properties / own_company_idAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The public id of the company this workspace is anchored to, or null. A row that carries it is a company workspace, the one the close flow runs in; a row without it is a membership workspace." +} - changed
Output schema / properties / workspaces / items / requiredPrevious value: -[ - "workspace_id", - "workspace_name", - "is_primary", - "has_bank_transactions", - "identity" -]New value: +[ + "workspace_id", + "workspace_name", + "is_primary", + "own_company_id", + "lineage_parent_workspace_id", + "has_bank_transactions", + "identity" +]
- Added
well_sum_invoices - Changed
well_switch_workspace7 fields changed- changed
Input schema / properties / ack / descriptionPrevious value: -"Acknowledge a flow step: \"connectors\" for the connect-tools step, \"bank\" for the bank step, \"categorize\" for the counterparty-categorization step, \"assign\" for the assign-owners step, \"deploy\" for the collect-agents step. The acknowledgement is scoped to the workspace this call is dispatched to, and a switch to another workspace clears it. An acknowledgement sent for a workspace this connection has switched away from is refused, and the pinned workspace's own acknowledgement of that step stands."New value: +"Acknowledge a flow step: \"connectors\" for the connect-tools step, \"bank\" for the bank step, \"categorize\" for the counterparty-categorization step, \"assign\" for the assign-owners step, \"deploy\" for the collect-agents step, \"retarget\" for the connector-retarget (data-migration) card. \"retarget\" is the retarget card's own ack: the card sends it on its Confirm with ack_outcome \"done\", or on its Keep for later with \"keep_for_later\", so you do not send it yourself. The acknowledgement is scoped to the workspace this call is dispatched to, and a switch to another workspace clears it. An acknowledgement sent for a workspace this connection has switched away from is refused, and the pinned workspace's own acknowledgement of that step stands." - changed
Input schema / properties / ack / enumPrevious value: -[ - "connectors", - "bank", - "categorize", - "assign", - "deploy" -]New value: +[ + "connectors", + "bank", + "categorize", + "assign", + "deploy", + "company_pick", + "accounting_settings", + "retarget" +] - changed
Input schema / properties / ack_outcome / descriptionPrevious value: -"What the click said about the step: \"done\" when the user carried it out, \"keep_for_later\" when they set it aside on purpose. REQUIRED with ack \"categorize\" and \"deploy\", whose cards end on two buttons that mean different things, and refused with every other step, whose card confirms and nothing else."New value: +"What the click said about the step: \"done\" when the user carried it out, \"keep_for_later\" when they set it aside on purpose. REQUIRED with ack \"categorize\" and \"deploy\" and \"retarget\", whose cards end on two buttons that mean different things, and refused with every other step, whose card confirms and nothing else." - added
Input schema / properties / recurring_contextsAdded value: +{ + "description": "The billing contexts the user counts as recurring revenue, stored as this session's selected_recurring_contexts and scoped to the workspace this call is dispatched to. Copy each context_key off the row well_list_recurring_contexts returned; never send a label. An EMPTY list is a real answer — it records that the user counts nothing as recurring — so send it when they clicked Continue with nothing ticked, and omit the field entirely when they have not answered. At most 100, the same bound well_render_mrr accepts. The session holds ONE set, so this REPLACES the previous one, and like the exemptions it survives a change of months.", + "items": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "maxItems": 100, + "type": "array" +} - changed
Output schema / properties / acknowledged / enumPrevious value: -[ - "connectors", - "bank", - "categorize", - "assign", - "deploy" -]New value: +[ + "connectors", + "bank", + "categorize", + "assign", + "deploy", + "company_pick", + "accounting_settings", + "retarget" +] - changed
Output schema / properties / changed / items / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "exemptions", - "connectors_ack", - "bank_ack", - "categorize_ack", - "assign_ack", - "deploy_ack", - "next_step" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "recurring_contexts", + "next_step", + "connectors_ack", + "bank_ack", + "categorize_ack", + "assign_ack", + "deploy_ack", + "company_pick", + "accounting_settings_ack", + "retarget_ack" +] - added
Output schema / properties / selected_recurring_contextsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +}
- Changed
well_update_person1 field changed- changed
Input schema / properties / job_title / anyOfPrevious value: -[ - { - "maxLength": 200, - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "type": "string" + }, + { + "type": "null" + } +]
- Changed
well_upsert_accounting_settings6 fields changed- added
Input schema / properties / incorporation_dateAdded value: +{ + "anyOf": [ + { + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The company's incorporation / registration date as YYYY-MM-DD, or null to clear it." +} - added
Input schema / properties / tax_id_typeAdded value: +{ + "description": "The tax id's type (SIREN, VAT, EIN, …). Provide it together with tax_id_value.", + "type": "string" +} - added
Input schema / properties / tax_id_valueAdded value: +{ + "description": "The company's tax id value. Provide it together with tax_id_type; one without the other is refused.", + "maxLength": 50, + "minLength": 1, + "type": "string" +} - added
Output schema / properties / incorporation_dateAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] +} - added
Output schema / properties / tax_id_typeAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] +} - added
Output schema / properties / tax_id_valueAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] +}
- Changed
well_wait_for_selection5 fields changed- changed
Input schema / properties / kind / descriptionPrevious value: -"Which card click to wait for: \"workspace\" (the workspace picker's Use), \"periods\" (the month picker's Validate), \"counterparties\" (the missing-invoices card's Continue), \"exemptions\" (the burn-exemption card's Continue), \"connect_ack\" / \"bank_ack\" (the connect card's Continue), \"categorize_ack\" (the categorize card's Continue or Keep for later), \"assign_ack\" (the owner-assignment card's Continue), \"deploy_ack\" (the collect-agents card's Deploy, Continue or Keep for later), \"next_step\" (a row click on the next-steps card)."New value: +"Which card click to wait for: \"workspace\" (the workspace picker's Use), \"periods\" (the month picker's Validate), \"counterparties\" (the missing-invoices card's Continue), \"exemptions\" (the burn-exemption card's Continue), \"recurring_contexts\" (the recurring-contexts card's Continue), \"connect_ack\" / \"bank_ack\" (the connect card's Continue), \"categorize_ack\" (the categorize card's Continue or Keep for later), \"assign_ack\" (the owner-assignment card's Continue), \"deploy_ack\" (the collect-agents card's Deploy, Continue or Keep for later), \"company_pick\" (the company-candidates card's Use this company), \"retarget_ack\" (the connector-retarget card's Confirm or Keep for later), \"next_step\" (a row click on the next-steps card)." - changed
Input schema / properties / kind / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "exemptions", - "connect_ack", - "bank_ack", - "categorize_ack", - "deploy_ack", - "assign_ack", - "next_step" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "recurring_contexts", + "connect_ack", + "bank_ack", + "categorize_ack", + "deploy_ack", + "assign_ack", + "accounting_settings_ack", + "next_step", + "company_pick", + "retarget_ack" +] - added
Input schema / properties / waiting_noticeAdded value: +{ + "description": "One short line the person reads while this call holds the turn open, IN THE LANGUAGE THEY ARE WRITING IN. Say what you are waiting for them to do on the card, in your own words, not what the server is doing: they are the one holding the flow. Omitted falls back to an English line, which a reader working in another language may not read, so write it whenever you know their language.", + "maxLength": 120, + "minLength": 1, + "type": "string" +} - changed
Output schema / properties / kind / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "exemptions", - "connect_ack", - "bank_ack", - "categorize_ack", - "deploy_ack", - "assign_ack", - "next_step" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "recurring_contexts", + "connect_ack", + "bank_ack", + "categorize_ack", + "deploy_ack", + "assign_ack", + "accounting_settings_ack", + "next_step", + "company_pick", + "retarget_ack" +] - added
Output schema / properties / selection / properties / recurring_contextsAdded value: +{ + "description": "The billing context keys the user counts as recurring on kind \"recurring_contexts\". An EMPTY array is the answer \"nothing is recurring\", not an absent one.", + "items": { + "type": "string" + }, + "type": "array" +}
17 tool updates
- Changed
well_enqueue_invoice_fetch1 field changed- changed
Output schema / properties / enqueued / items / properties / provider / anyOfPrevious value: -[ - { - "additionalProperties": false, - "properties": { - "countries": { - "items": { - "type": "string" - }, - "type": "array" - }, - "has_blueprint": { - "type": "boolean" - }, - "id": { - "format": "uuid", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", - "type": "string" - }, - "logo_url": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "name": { - "type": "string" - }, - "slug": { - "type": "string" - }, - "url": { - "type": "string" - } - }, - "required": [ - "id", - "name", - "slug", - "url", - "logo_url", - "countries", - "has_blueprint" - ], - "type": "object" - }, - { - "type": "null" - } -]New value: +[ + { + "additionalProperties": false, + "properties": { + "countries": { + "items": { + "type": "string" + }, + "type": "array" + }, + "has_blueprint": { + "type": "boolean" + }, + "has_portal_url": { + "type": "boolean" + }, + "id": { + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", + "type": "string" + }, + "logo_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "slug", + "url", + "logo_url", + "countries", + "has_blueprint", + "has_portal_url" + ], + "type": "object" + }, + { + "type": "null" + } +]
- Removed
well_get_cash_flow_bridge - Added
well_get_connector_coverage - Added
well_get_session_digest - Added
well_get_worklist_status - Changed
well_list_account_balances2 fields changed- added
Output schema / properties / rows / items / properties / duplicate_of_account_idAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] +} - changed
Output schema / properties / rows / items / requiredPrevious value: -[ - "account_id", - "account_name", - "account_type", - "account_subtype", - "ownership", - "company_id", - "company_name", - "account_currency", - "institution_name", - "masked_account_number", - "balance", - "verification_rejected" -]New value: +[ + "account_id", + "account_name", + "account_type", + "account_subtype", + "ownership", + "company_id", + "company_name", + "account_currency", + "institution_name", + "masked_account_number", + "duplicate_of_account_id", + "balance", + "verification_rejected" +]
- Changed
well_list_cash_scope2 fields changed- added
Output schema / properties / folded_duplicatesAdded value: +{ + "type": "number" +} - changed
Output schema / requiredPrevious value: -[ - "groups", - "excluded_not_owned", - "unsettled_ownership", - "unreadable_balances", - "unreadable_currency", - "partial", - "success" -]New value: +[ + "groups", + "excluded_not_owned", + "unsettled_ownership", + "unreadable_balances", + "unreadable_currency", + "folded_duplicates", + "partial", + "success" +]
- Changed
well_list_missing_invoices2 fields changed- changed
Output schema / properties / rows / items / properties / mode / descriptionPrevious value: -"How the app's card offers to obtain the invoice: agent (a browser agent runs the supplier portal — a published blueprint exists), connect (connect matched_connector_service_id and Well fetches it), upload (the user supplies the file). Already downgraded from suggested_action when the automated route cannot be honoured."New value: +"How the app's card offers to obtain the invoice: agent (a browser agent runs the supplier portal — the provider carries a blueprint OR a real portal URL the agent runs against), connect (connect matched_connector_service_id and Well fetches it), upload (the user supplies the file). Decided from the provider facts: connect wins when a connector matched, else agent when it can run, else upload." - changed
Output schema / properties / rows / items / properties / suggested_action / descriptionPrevious value: -"The backend's raw routing decision, before the downgrade rules."New value: +"The backend's routing decision for the row. `chrome_extension_fetch` when the matched provider can run the browser agent (a blueprint or a real portal URL), `connect_provider` when a connector matched, else `manual_upload`. `mode` is the same decision in the card's vocabulary."
- Changed
well_list_workspaces1 field changed- removed
Output schema / properties / next_stepRemoved value: -{ - "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.", - "type": "string" -}
- Added
well_propose_next_steps - Changed
well_query_records1 field changed- changed
Input schema / properties / fields / descriptionPrevious value: -"EXTRA field paths to add to the root's display view, for values you need to reason about. Each path is an array whose first segment is the root's table name — use the paths well_get_schema(root) returns verbatim, which is the root name for every root except people (whose table is peoples); a path opening with any other segment is dropped. Additive only: they widen the payload you receive, and the columns the user sees stay the root's display view (the ones the Well web app shows) no matter what you pass here. A scalar a composite renders comes back AS that composite — asking for grand_total gets you composite_total_amount_currency, with grand_total inside it — so read `columns` for what was actually materialized. Omit unless you need a value the display view does not carry."New value: +"EXTRA field paths to add to the root's display view, for values you need to reason about. Each path is an array whose first segment is the root's table name — use the paths well_get_schema(root) returns verbatim, which is the root name for every root except people (whose table is peoples); a path opening with any other segment is dropped. Additive only: they widen the payload you receive, and the root's own display projection (the columns the Well web app shows, and the ones a table drawn from this query carries) stays what it is no matter what you pass here. A scalar a composite renders comes back AS that composite — asking for grand_total gets you composite_total_amount_currency, with grand_total inside it — so read `columns` for what was actually materialized. Omit unless you need a value the display view does not carry."
- Added
well_render_cash_flow_bridge - Added
well_show_records - Added
well_show_workspace_picker - Changed
well_sum_transactions4 fields changed- added
Input schema / properties / scopeAdded value: +{ + "description": "Which rows are this workspace's: `own` for a sum reconciled against its own balances, `own_and_adopted` for a burn. Required; see the description.", + "enum": [ + "own", + "own_and_adopted" + ], + "type": "string" +} - changed
Input schema / requiredPrevious value: -[ - "from", - "to" -]New value: +[ + "from", + "to", + "scope" +] - added
Output schema / properties / excluded_no_owned_legAdded value: +{ + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] +} - changed
Output schema / requiredPrevious value: -[ - "rows", - "window", - "excluded_malformed", - "excluded_zero_leg", - "excluded_multi_leg", - "partial", - "success" -]New value: +[ + "rows", + "window", + "excluded_malformed", + "excluded_zero_leg", + "excluded_multi_leg", + "excluded_no_owned_leg", + "partial", + "success" +]
- Changed
well_switch_workspace3 fields changed- added
Input schema / properties / next_stepAdded value: +{ + "additionalProperties": false, + "description": "The next step the user picked on the well_propose_next_steps card, as the pair that card listed: the skill's slug and the sentence beside it. Both travel, because the sentence is what the model reads as the user's own message and the slug is what it loads. The pick is scoped to the workspace this call is dispatched to, and a switch to another workspace clears it.", + "properties": { + "prompt": { + "description": "The sentence the card listed beside the skill, copied as written.", + "maxLength": 160, + "minLength": 1, + "type": "string" + }, + "skill": { + "description": "The slug of the skill the picked line offers, exactly as the card listed it.", + "pattern": "^[a-z0-9-]{1,64}$", + "type": "string" + } + }, + "required": [ + "skill", + "prompt" + ], + "type": "object" +} - changed
Output schema / properties / changed / items / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "exemptions", - "connectors_ack", - "bank_ack", - "categorize_ack", - "assign_ack", - "deploy_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "connectors_ack", + "bank_ack", + "categorize_ack", + "assign_ack", + "deploy_ack", + "next_step" +] - added
Output schema / properties / selected_next_stepAdded value: +{ + "additionalProperties": false, + "description": "The next step this call recorded, echoed back as the card sent it.", + "properties": { + "prompt": { + "description": "The sentence the card listed beside the skill, copied as written.", + "maxLength": 160, + "minLength": 1, + "type": "string" + }, + "skill": { + "description": "The slug of the skill the picked line offers, exactly as the card listed it.", + "pattern": "^[a-z0-9-]{1,64}$", + "type": "string" + } + }, + "required": [ + "skill", + "prompt" + ], + "type": "object" +}
- Changed
well_wait_for_selection4 fields changed- changed
Input schema / properties / kind / descriptionPrevious value: -"Which card click to wait for: \"workspace\" (the workspace picker's Use), \"periods\" (the month picker's Validate), \"counterparties\" (the missing-invoices card's Continue), \"exemptions\" (the burn-exemption card's Continue), \"connect_ack\" / \"bank_ack\" (the connect card's Continue), \"categorize_ack\" (the categorize card's Continue or Keep for later), \"assign_ack\" (the owner-assignment card's Continue), \"deploy_ack\" (the collect-agents card's Deploy, Continue or Keep for later)."New value: +"Which card click to wait for: \"workspace\" (the workspace picker's Use), \"periods\" (the month picker's Validate), \"counterparties\" (the missing-invoices card's Continue), \"exemptions\" (the burn-exemption card's Continue), \"connect_ack\" / \"bank_ack\" (the connect card's Continue), \"categorize_ack\" (the categorize card's Continue or Keep for later), \"assign_ack\" (the owner-assignment card's Continue), \"deploy_ack\" (the collect-agents card's Deploy, Continue or Keep for later), \"next_step\" (a row click on the next-steps card)." - changed
Input schema / properties / kind / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "exemptions", - "connect_ack", - "bank_ack", - "categorize_ack", - "deploy_ack", - "assign_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "connect_ack", + "bank_ack", + "categorize_ack", + "deploy_ack", + "assign_ack", + "next_step" +] - changed
Output schema / properties / kind / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "exemptions", - "connect_ack", - "bank_ack", - "categorize_ack", - "deploy_ack", - "assign_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "connect_ack", + "bank_ack", + "categorize_ack", + "deploy_ack", + "assign_ack", + "next_step" +] - added
Output schema / properties / selection / properties / next_stepAdded value: +{ + "additionalProperties": false, + "description": "The row the user picked on kind \"next_step\".", + "properties": { + "prompt": { + "description": "The sentence the picked row carried, to be treated as the user's own message.", + "type": "string" + }, + "skill": { + "description": "The slug of the skill the picked row offers.", + "type": "string" + } + }, + "required": [ + "skill", + "prompt" + ], + "type": "object" +}
18 tool updates
- Changed
well_enqueue_invoice_fetch1 field changed- changed
Output schema / properties / skipped / items / properties / reason / enumPrevious value: -[ - "company_not_found_in_workspace" -]New value: +[ + "company_not_found_in_workspace", + "not_owned_by_current_user" +]
- Removed
well_get_cash_position - Removed
well_get_runway - Added
well_list_account_balances - Changed
well_list_burn_exemptions1 field changed- added
Output schema / properties / next_stepAdded value: +{ + "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.", + "type": "string" +}
- Added
well_list_cash_scope - Changed
well_list_connectors1 field changed- added
Output schema / properties / next_stepAdded value: +{ + "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.", + "type": "string" +}
- Changed
well_list_counterparties1 field changed- added
Output schema / properties / next_stepAdded value: +{ + "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.", + "type": "string" +}
- Changed
well_list_member_candidates5 fields changed- added
Input schema / properties / calendar_monthAdded value: +{ + "description": "Calendar month, 1 = January … 12 = December. `from_assigned_gaps` only, paired with `calendar_year`.", + "maximum": 12, + "minimum": 1, + "type": "integer" +} - added
Input schema / properties / calendar_yearAdded value: +{ + "description": "Calendar year, e.g. 2026. `from_assigned_gaps` only.", + "maximum": 2100, + "minimum": 2000, + "type": "integer" +} - added
Input schema / properties / fiscal_periodAdded value: +{ + "description": "Fiscal period, 1-12. `from_assigned_gaps` only, paired with `fiscal_year`. The adjustment period (13) is refused.", + "maximum": 13, + "minimum": 1, + "type": "integer" +} - added
Input schema / properties / fiscal_yearAdded value: +{ + "description": "Fiscal year (the calendar year the workspace's fiscal year STARTED in). `from_assigned_gaps` only.", + "maximum": 2100, + "minimum": 2000, + "type": "integer" +} - added
Input schema / properties / from_assigned_gapsAdded value: +{ + "description": "Resolve the candidates from the owners of the period's missing-invoice gaps, server-side, instead of `person_ids`. The detected teammates are omitted, and `include_detected` is treated as false.", + "type": "boolean" +}
- Changed
well_list_missing_invoice_owners4 fields changed- added
Output schema / properties / next_stepAdded value: +{ + "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.", + "type": "string" +} - added
Output schema / properties / transactions / items / properties / counterparty / properties / matched_connector_service_idAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The connector standing for a `connect` route; null for a route with no matched connector." +} - added
Output schema / properties / transactions / items / properties / counterparty / properties / modeAdded value: +{ + "anyOf": [ + { + "enum": [ + "agent", + "connect", + "upload" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "description": "How the missing document can be collected, after the same downgrade rules the missing-invoices card applies; null when no route resolved. Word the retrieval affordance off this, never off a name." +} - changed
Output schema / properties / transactions / items / properties / counterparty / requiredPrevious value: -[ - "company_id", - "name", - "logo_url" -]New value: +[ + "company_id", + "name", + "logo_url", + "mode", + "matched_connector_service_id" +]
- Changed
well_list_missing_invoices1 field changed- added
Output schema / properties / next_stepAdded value: +{ + "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.", + "type": "string" +}
- Changed
well_list_periods4 fields changed- changed
Input schema / properties / purpose / descriptionPrevious value: -"Why the months are being listed, set by the calling skill's own flow — never inferred from this message. \"close\" is book closure: the card paints close readiness, and each month carries its per-day `close_days` and the counts behind its \"why not ready\" errands. \"analysis\" is reporting: the card offers only the months a canvas can report on, paints no day axis, counts on the execution date the canvas aggregates measure on, and therefore omits the books-dated errand counts entirely rather than reporting them as 0 — set it ONLY when a reporting skill is already running its own period-pick step, not just because this message names one (a burn figure, a cost breakdown, \"my report\"). Naming a report is phrasing; it never sets this field on its own. Omit or \"collect\" for invoice retrieval (the default, and the right choice for a plain \"what months do you have\" question, even one that mentions why) — this paints the retrieval axis and skips the close-readiness fields."New value: +"Why the months are being listed, set by the calling skill's own flow — never inferred from this message. \"close\" is book closure: the card paints close readiness, and each month carries its per-day `close_days` and the counts behind its \"why not ready\" errands. \"analysis\" is reporting: the card offers only the months a canvas can report on, paints the categorization day axis, counts on the execution date the canvas aggregates measure on, and therefore omits the books-dated errand counts entirely rather than reporting them as 0 — set it ONLY when a reporting skill is already running its own period-pick step, not just because this message names one (a burn figure, a cost breakdown, \"my report\"). Naming a report is phrasing; it never sets this field on its own. Omit or \"collect\" for invoice retrieval (the default, and the right choice for a plain \"what months do you have\" question, even one that mentions why) — this paints the retrieval axis and skips the close-readiness fields." - added
Output schema / properties / next_stepAdded value: +{ + "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.", + "type": "string" +} - added
Output schema / properties / periods / items / properties / analysis_daysAdded value: +{ + "description": "Days of the month and whether a breakdown can name what each one holds, ascending. Present ONLY for a \"analysis\" call. \"uncategorized\" means the day holds a transaction with no category — still counted in a burn total, but reported as uncategorised in a breakdown rather than under a named category; \"categorized\" means they all carry one; \"neutral\" means the day holds no transaction. Unlike the other two axes, a quiet day IS listed, as \"neutral\" — so a day absent from a non-empty list is one the read did not reach. An EMPTY list means the month was not measured at all, never that it holds nothing to categorize: check transaction_count, which is read separately, and treat a positive count beside an empty list as unmeasured. Calendar detail for the picker; quote the month's own counts in prose, not a day list.", + "items": { + "additionalProperties": false, + "properties": { + "day": { + "maximum": 31, + "minimum": 1, + "type": "integer" + }, + "state": { + "enum": [ + "categorized", + "uncategorized", + "neutral" + ], + "type": "string" + } + }, + "required": [ + "day", + "state" + ], + "type": "object" + }, + "type": "array" +} - changed
Output schema / properties / periods / items / properties / days / descriptionPrevious value: -"Days of the month carrying a retrieval state, ascending. A day absent from this list is \"none\"; the list is empty whenever invoice_state is \"none\", and is ALWAYS empty on a \"analysis\" call, which paints no day axis — so an empty list there carries no invoice claim."New value: +"Days of the month carrying a retrieval state, ascending. A day absent from this list is \"none\"; the list is empty whenever invoice_state is \"none\", and is ALWAYS empty on a \"analysis\" call, which paints the categorization axis instead — so an empty list there carries no invoice claim."
- Changed
well_list_workspaces1 field changed- added
Output schema / properties / next_stepAdded value: +{ + "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.", + "type": "string" +}
- Changed
well_preview_invoice_fetch2 fields changed- added
Output schema / properties / next_stepAdded value: +{ + "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.", + "type": "string" +} - added
Output schema / properties / rows_dropped_by_ownershipAdded value: +{ + "description": "Counterparty rows this preview dropped because the current user owns none of their gaps — a fetch runs as the current user and can only collect the invoices of counterparties they own. Counted apart from `selection_scope.rows_dropped_by_filter` (the pick's own shortfall), and present only when it dropped at least one. The matching `hints` line names it.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" +}
- Added
well_render_cash_position - Added
well_render_runway - Changed
well_switch_workspace9 fields changed- changed
Input schema / properties / ack / descriptionPrevious value: -"Acknowledge a flow step: \"connectors\" for the connect-tools step, \"bank\" for the bank step. The acknowledgement is scoped to the workspace this call is dispatched to, and a switch to another workspace clears it. An acknowledgement sent for a workspace this connection has switched away from is refused, and the pinned workspace's own acknowledgement of that step stands."New value: +"Acknowledge a flow step: \"connectors\" for the connect-tools step, \"bank\" for the bank step, \"categorize\" for the counterparty-categorization step, \"assign\" for the assign-owners step, \"deploy\" for the collect-agents step. The acknowledgement is scoped to the workspace this call is dispatched to, and a switch to another workspace clears it. An acknowledgement sent for a workspace this connection has switched away from is refused, and the pinned workspace's own acknowledgement of that step stands." - changed
Input schema / properties / ack / enumPrevious value: -[ - "connectors", - "bank" -]New value: +[ + "connectors", + "bank", + "categorize", + "assign", + "deploy" +] - added
Input schema / properties / ack_outcomeAdded value: +{ + "description": "What the click said about the step: \"done\" when the user carried it out, \"keep_for_later\" when they set it aside on purpose. REQUIRED with ack \"categorize\" and \"deploy\", whose cards end on two buttons that mean different things, and refused with every other step, whose card confirms and nothing else.", + "enum": [ + "done", + "keep_for_later" + ], + "type": "string" +} - changed
Output schema / properties / acknowledged / enumPrevious value: -[ - "connectors", - "bank" -]New value: +[ + "connectors", + "bank", + "categorize", + "assign", + "deploy" +] - added
Output schema / properties / acknowledged_outcomeAdded value: +{ + "description": "The answer the acknowledging click carried, echoed back on a step whose card offers two.", + "enum": [ + "done", + "keep_for_later" + ], + "type": "string" +} - changed
Output schema / properties / changed / items / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "exemptions", - "connectors_ack", - "bank_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "connectors_ack", + "bank_ack", + "categorize_ack", + "assign_ack", + "deploy_ack" +] - added
Output schema / properties / pickupAdded value: +{ + "description": "What became of this write: \"resumed\" (a model wait took it), \"held_then_resumed\" (no wait yet, so the call was held until one consumed it), \"unwaited\" (nothing ever consumed it), \"exhausted\" (the model gave up on the card and ended the turn), \"stale\" (the flow already answered this card and moved past it — the value is recorded as a late edit). A card prefills a reply on \"unwaited\" and \"exhausted\" only.", + "enum": [ + "resumed", + "held_then_resumed", + "unwaited", + "exhausted", + "stale" + ], + "type": "string" +} - added
Output schema / properties / resumedAdded value: +{ + "description": "True when the model's own turn carries this write on — the derived half of `pickup` (\"resumed\" or \"held_then_resumed\"). A card that reads true must not prefill a reply; the conversation continues on its own.", + "type": "boolean" +} - changed
Output schema / requiredPrevious value: -[ - "success" -]New value: +[ + "success", + "resumed", + "pickup" +]
- Changed
well_wait_for_selection6 fields changed- changed
Input schema / properties / kind / descriptionPrevious value: -"Which card click to wait for: \"workspace\" (the workspace picker's Use), \"periods\" (the month picker's Validate), \"counterparties\" (the missing-invoices card's Continue), \"exemptions\" (the burn-exemption card's Continue), \"connect_ack\" / \"bank_ack\" (the connect card's Continue)."New value: +"Which card click to wait for: \"workspace\" (the workspace picker's Use), \"periods\" (the month picker's Validate), \"counterparties\" (the missing-invoices card's Continue), \"exemptions\" (the burn-exemption card's Continue), \"connect_ack\" / \"bank_ack\" (the connect card's Continue), \"categorize_ack\" (the categorize card's Continue or Keep for later), \"assign_ack\" (the owner-assignment card's Continue), \"deploy_ack\" (the collect-agents card's Deploy, Continue or Keep for later)." - changed
Input schema / properties / kind / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "exemptions", - "connect_ack", - "bank_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "connect_ack", + "bank_ack", + "categorize_ack", + "deploy_ack", + "assign_ack" +] - changed
Input schema / properties / timeout_s / descriptionPrevious value: -"How long to wait, in seconds. Default 10, clamped to 5-90."New value: +"How long to wait, in seconds. Default 60, clamped to 5-60." - changed
Output schema / properties / already_set / descriptionPrevious value: -"True when the selection was already recorded before this call started — no wait happened."New value: +"True when the click had already been recorded since the card was drawn, before this call started — no wait happened." - changed
Output schema / properties / kind / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "exemptions", - "connect_ack", - "bank_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "connect_ack", + "bank_ack", + "categorize_ack", + "deploy_ack", + "assign_ack" +] - added
Output schema / properties / selection / properties / outcomeAdded value: +{ + "description": "What the acknowledging click said about the step: \"done\" when the user carried it out, \"keep_for_later\" when they set it aside on purpose. Both end the step and continue the flow. Absent on a card whose one action confirms and nothing else.", + "enum": [ + "done", + "keep_for_later" + ], + "type": "string" +}
4 tool updates
- Added
well_list_accounts_needing_company - Added
well_list_unposted_transactions - Added
well_set_transaction_category - Added
well_set_transaction_ledger_account
7 tool updates
- Added
well_assign_missing_invoice_owners - Added
well_get_skill - Added
well_invite_members - Added
well_list_member_candidates - Added
well_list_missing_invoice_owners - Changed
well_list_missing_invoices2 fields changed- added
Output schema / properties / rows / items / properties / matched_provider_urlAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] +} - changed
Output schema / properties / rows / items / requiredPrevious value: -[ - "id", - "company_id", - "name", - "logo_url", - "domain_resolution", - "months", - "tx_count", - "transactions", - "transactions_omitted", - "base_total_amount", - "mode", - "available_modes", - "suggested_action", - "matched_provider_name", - "matched_provider_has_blueprint", - "matched_connector_service_id" -]New value: +[ + "id", + "company_id", + "name", + "logo_url", + "domain_resolution", + "months", + "tx_count", + "transactions", + "transactions_omitted", + "base_total_amount", + "mode", + "available_modes", + "suggested_action", + "matched_provider_name", + "matched_provider_url", + "matched_provider_has_blueprint", + "matched_connector_service_id" +]
- Added
well_search_skill
3 tool updates
- Added
well_enqueue_invoice_fetch - Changed
well_list_missing_invoices1 field changed- changed
Output schema / properties / rows / items / properties / logo_url / descriptionPrevious value: -"The counterparty's mark, resolved in that order: the matched provider's stored logo, else the counterparty company's own stored logo — the one company enrichment resolved from that company's domain — else the mark derived from the matched portal's host. Null when the row matched no portal and its company carries no logo, and the card then draws initials."New value: +"The counterparty's mark: the matched provider's stored logo, else the counterparty company's own stored logo — the one company enrichment resolved from that company's domain. Null when the row matched no provider and its company carries no stored logo. A null here does not mean the card draws initials: the widget is additionally offered a mark derived from the company's own host, which this field never carries."
- Changed
well_preview_invoice_fetch1 field changed- changed
Output schema / properties / vendors / items / properties / logo_url / descriptionPrevious value: -"The vendor's mark; null when neither the catalog nor its host resolved one."New value: +"The provider's logo as Well stores it. Null when the provider is unmatched or Well holds no stored mark for it, in which case the card still renders one from `domain`."
13 tool updates
- Added
well_assign_account - Removed
well_get_burn - Changed
well_get_cost_structure3 fields changed- added
Output schema / properties / entries / items / properties / category_keyAdded value: +{ + "description": "The category-catalog key this slice groups. Present only when `rung` is \"category_key\", and never on the rolled-up \"Other\" slice. It is the value `well_sum_transactions` accepts in `exempt_categories`, so exclude a category by this key rather than by its label.", + "type": "string" +} - changed
Output schema / properties / rung / descriptionPrevious value: -"Which ladder rung produced these categories: the workspace's own ledger accounts, Well's auto-categorization, a technical fallback bucket, or none qualified."New value: +"Which ladder rung produced these categories: \"ledger_account\" (the workspace's own chart of accounts), \"category_key\" (Well's category catalog, and the only rung whose entries carry `category_key`), \"category_normalized\" (Well's stored category label), \"transaction_type\" (a technical fallback bucket), or \"uncategorised\" (no rung qualified)." - changed
Output schema / properties / rung / enumPrevious value: -[ - "ledger_account", - "category_normalized", - "transaction_type", - "uncategorised" -]New value: +[ + "ledger_account", + "category_key", + "category_normalized", + "transaction_type", + "uncategorised" +]
- Changed
well_get_statement_import_result3 fields changed- added
Output schema / properties / counterparties_pendingAdded value: +{ + "description": "Whether any minted transaction still awaits its counterparty. True right after import while the from/to parties resolve in the background; absent when the result carries no minted transactions.", + "type": "boolean" +} - added
Output schema / properties / phaseAdded value: +{ + "anyOf": [ + { + "enum": [ + "queued", + "extracting", + "categorized", + "importing" + ], + "type": "string" + }, + { + "type": "null" + } + ] +} - added
Output schema / properties / reasonAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] +}
- Added
well_list_burn_exemptions - Changed
well_list_connectors4 fields changed- added
Output schema / properties / limit / descriptionAdded value: +"The page size that was REQUESTED. The catalog may return fewer." - added
Output schema / properties / offset / descriptionAdded value: +"How many catalog rows this page skipped." - added
Output schema / properties / page_countAdded value: +{ + "description": "The catalog page's own length, and the ONLY safe paging cursor: advance by `offset + page_count`. `connectors` can be LONGER — on the first page of an unsearched browse the workspace's already-connected rows are prepended so they cannot be lost to the catalog's ordering — so paging by the array's length silently skips exactly that many catalog rows on every later request.", + "type": "number" +} - added
Output schema / properties / total / descriptionAdded value: +"Every connector matching the query, across all pages — NOT the length of `connectors`."
- Changed
well_list_missing_invoices2 fields changed- added
Output schema / properties / rows / items / properties / domain_resolutionAdded value: +{ + "description": "The counterparty company's own domain-discovery state: 'resolved' when it already carries a domain (any source); 'pending' when a web-evidence resolve is durably enqueued and still open; 'none' when no domain is known and nothing is in flight.", + "enum": [ + "resolved", + "pending", + "none" + ], + "type": "string" +} - changed
Output schema / properties / rows / items / requiredPrevious value: -[ - "id", - "company_id", - "name", - "logo_url", - "months", - "tx_count", - "transactions", - "transactions_omitted", - "base_total_amount", - "mode", - "available_modes", - "suggested_action", - "matched_provider_name", - "matched_provider_has_blueprint", - "matched_connector_service_id" -]New value: +[ + "id", + "company_id", + "name", + "logo_url", + "domain_resolution", + "months", + "tx_count", + "transactions", + "transactions_omitted", + "base_total_amount", + "mode", + "available_modes", + "suggested_action", + "matched_provider_name", + "matched_provider_has_blueprint", + "matched_connector_service_id" +]
- Changed
well_list_periods10 fields changed- changed
Input schema / properties / purpose / descriptionPrevious value: -"Why the months are being listed, set by the calling skill (not guessed from the user's words). \"close\" is book closure: the card paints close readiness, and each month carries its per-day `close_days` and the counts behind its \"why not ready\" errands. Omit or \"collect\" for invoice retrieval (the default), which paints the retrieval axis and skips the close-readiness fields."New value: +"Why the months are being listed, set by the calling skill's own flow — never inferred from this message. \"close\" is book closure: the card paints close readiness, and each month carries its per-day `close_days` and the counts behind its \"why not ready\" errands. \"analysis\" is reporting: the card offers only the months a canvas can report on, paints no day axis, counts on the execution date the canvas aggregates measure on, and therefore omits the books-dated errand counts entirely rather than reporting them as 0 — set it ONLY when a reporting skill is already running its own period-pick step, not just because this message names one (a burn figure, a cost breakdown, \"my report\"). Naming a report is phrasing; it never sets this field on its own. Omit or \"collect\" for invoice retrieval (the default, and the right choice for a plain \"what months do you have\" question, even one that mentions why) — this paints the retrieval axis and skips the close-readiness fields." - changed
Input schema / properties / purpose / enumPrevious value: -[ - "close", - "collect" -]New value: +[ + "close", + "collect", + "analysis" +] - added
Output schema / properties / periods / items / properties / analyzableAdded value: +{ + "description": "The month can be REPORTED ON — an average-burn, cost-structure or cash-bridge figure can be computed for it. True once the month has ENDED and while it stays inside the window the canvas endpoints serve. Read this one for an analysis pick.", + "type": "boolean" +} - changed
Output schema / properties / periods / items / properties / categorized_unposted_transactions / descriptionPrevious value: -"Categorized transactions not yet posted to the ledger — part of the 'review and book' close errand."New value: +"Categorized transactions not yet posted to the ledger — part of the 'review and book' close errand. Dated on the books, and likewise ABSENT rather than 0 on a \"analysis\" call." - changed
Output schema / properties / periods / items / properties / days / descriptionPrevious value: -"Days of the month carrying a retrieval state, ascending. A day absent from this list is \"none\"; the list is empty whenever invoice_state is \"none\"."New value: +"Days of the month carrying a retrieval state, ascending. A day absent from this list is \"none\"; the list is empty whenever invoice_state is \"none\", and is ALWAYS empty on a \"analysis\" call, which paints no day axis — so an empty list there carries no invoice claim." - added
Output schema / properties / periods / items / properties / invoice_state / descriptionAdded value: +"The month's invoice-retrieval verdict. ABSENT on a month that has BEGUN and whose verdict nothing measured, which only a purpose counting on the execution date produces — its activity read and this books-dated errand disagree, so \"none\" there would be a claim nothing wrote. A month still ahead of today keeps \"none\" on every purpose, because it owes nothing on every basis. Read an absent one from a \"close\" or \"collect\" list." - changed
Output schema / properties / periods / items / properties / missing_invoice_count / descriptionPrevious value: -"Counterparties owing a supplier invoice for the month; 0 when invoice_state is none."New value: +"Counterparties owing a supplier invoice for the month; 0 when invoice_state is \"none\". Absent whenever invoice_state is, and for the same reason." - added
Output schema / properties / periods / items / properties / transaction_count / descriptionAdded value: +"How many transactions the month holds, dated on the basis this purpose measures on: \"analysis\" counts on execution, every other purpose on the books. A presence signal for the month, never the row count behind a reported figure." - changed
Output schema / properties / periods / items / properties / uncategorized_transactions / descriptionPrevious value: -"Transactions in the month not yet categorized — the 'help categorize' close errand."New value: +"Transactions in the month not yet categorized — the 'help categorize' close errand. Dated on the books, so it is ABSENT on a \"analysis\" call rather than 0: that purpose counts on execution and never measures this errand." - changed
Output schema / properties / periods / items / requiredPrevious value: -[ - "calendar_year", - "calendar_month", - "fiscal_year", - "fiscal_period", - "label", - "is_complete", - "selectable", - "inspectable", - "close_status", - "close_reason", - "invoice_state", - "transaction_count", - "bank_transaction_count", - "unposted_invoice_count", - "uncategorized_transactions", - "categorized_unposted_transactions", - "missing_invoice_count", - "days" -]New value: +[ + "calendar_year", + "calendar_month", + "fiscal_year", + "fiscal_period", + "label", + "is_complete", + "selectable", + "inspectable", + "analyzable", + "close_status", + "close_reason", + "transaction_count", + "bank_transaction_count", + "unposted_invoice_count", + "days" +]
- Added
well_list_uncategorized_window - Added
well_render_burn - Added
well_sum_transactions - Changed
well_switch_workspace3 fields changed- added
Input schema / properties / exempt_categoriesAdded value: +{ + "description": "The categories the user marked as NOT burn, stored as this session's selected_exemptions and scoped to the workspace this call is dispatched to. Copy each category_key off the row well_list_burn_exemptions returned; never send a label. An EMPTY list is a real answer — it records that the user exempted nothing — so send it when they clicked Continue with nothing ticked, and omit the field entirely when they have not answered. At most 100, the same bound well_sum_transactions accepts, so a recorded answer is always one the sum can run. The session holds ONE set, so this REPLACES the previous one; unlike a counterparty pick it survives a change of months, because a category is or is not burn for the business whatever window is read next.", + "items": { + "maxLength": 200, + "minLength": 1, + "type": "string" + }, + "maxItems": 100, + "type": "array" +} - changed
Output schema / properties / changed / items / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "connectors_ack", - "bank_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "connectors_ack", + "bank_ack" +] - added
Output schema / properties / selected_exemptionsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +}
- Changed
well_wait_for_selection4 fields changed- changed
Input schema / properties / kind / descriptionPrevious value: -"Which card click to wait for: \"workspace\" (the workspace picker's Use), \"periods\" (the month picker's Validate), \"counterparties\" (the missing-invoices card's Continue), \"connect_ack\" / \"bank_ack\" (the connect card's Continue)."New value: +"Which card click to wait for: \"workspace\" (the workspace picker's Use), \"periods\" (the month picker's Validate), \"counterparties\" (the missing-invoices card's Continue), \"exemptions\" (the burn-exemption card's Continue), \"connect_ack\" / \"bank_ack\" (the connect card's Continue)." - changed
Input schema / properties / kind / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "connect_ack", - "bank_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "connect_ack", + "bank_ack" +] - changed
Output schema / properties / kind / enumPrevious value: -[ - "workspace", - "periods", - "counterparties", - "connect_ack", - "bank_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "exemptions", + "connect_ack", + "bank_ack" +] - added
Output schema / properties / selection / properties / exempt_categoriesAdded value: +{ + "description": "The category keys the user marked as NOT burn on kind \"exemptions\". An EMPTY array is the answer \"nothing is exempt\", not an absent one — status \"selected\" is what says the user answered.", + "items": { + "type": "string" + }, + "type": "array" +}
7 tool updates
- Changed
well_list_counterparties9 fields changed- changed
Input schema / properties / cursor / descriptionPrevious value: -"The next page of the uncategorized sweep: pass back the `next_cursor` the previous call returned. Only the sweep pages, so this needs `uncategorized_only: true`."New value: +"The next page of the workspace-wide uncategorized sweep: pass back the `next_cursor` the previous call returned. Only that sweep pages, so this needs `uncategorized_only: true` and NO `periods`, because a period scope returns every month it covers in one call." - changed
Input schema / properties / uncategorized_only / descriptionPrevious value: -"Instead of a period, sweep the WHOLE workspace for counterparties that carry no industry category. Returns 50 rows per page plus the total, and a `next_cursor` for the page after this one."New value: +"Keep only the counterparties that carry no industry category. WITH `periods`: the uncategorized counterparties OF those months. Use it whenever the question is which of a period's suppliers still need a category. WITHOUT `periods`: a WORKSPACE-WIDE sweep for every uncategorized counterparty, 50 rows per page plus the total, with a `next_cursor` for the page after this one." - changed
Output schema / properties / categorized_count / descriptionPrevious value: -"Rows in this result that carry at least one category."New value: +"COUNTERPARTIES of the scope that carry at least one category, counted over the whole scope, not over `rows`, and counted once however many months a counterparty appears in. Under `uncategorized_only` these are exactly the counterparties the result withheld, so a non-zero figure beside rows that are all uncategorized is the coverage, not a contradiction." - changed
Output schema / properties / mode / descriptionPrevious value: -"Which scope the call asked for; the row fields that are populated follow from it."New value: +"Which SCOPE the call asked for; the row fields that are populated follow from it. `periods` whenever the call named months, whether or not it also filtered to the uncategorized ones." - added
Output schema / properties / rows / items / properties / suggested_categoriesAdded value: +{ + "description": "A stored classifier PROPOSAL is a candidate label, never a decision: `categories` is what this counterparty actually carries, and only a write makes a proposal true. Highest confidence first, at most three. Empty when the classifier has not run, abstained, or a human already resolved its proposal — an empty array is not evidence that no label fits. Present on both scopes. Report a proposal as a suggestion to the user, never as the counterparty's industry, and NOTE this is unrelated to `suggested_retrieval`, which is about fetching invoices.", + "items": { + "additionalProperties": false, + "properties": { + "category_id": { + "description": "The catalog id of the proposed label — pass it straight to `well_update_company` to accept it.", + "type": "string" + }, + "confidence": { + "description": "0..1, the classifier's own scale.", + "type": "number" + }, + "name": { + "type": "string" + }, + "rank": { + "description": "1 is the classifier's top proposal.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "reasoning": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The classifier's own account of this proposal, or null when the run stored none." + } + }, + "required": [ + "category_id", + "name", + "confidence", + "rank", + "reasoning" + ], + "type": "object" + }, + "type": "array" +} - changed
Output schema / properties / rows / items / requiredPrevious value: -[ - "company_id", - "name", - "domain", - "logo_url", - "calendar_year", - "calendar_month", - "period_label", - "tx_count", - "base_total_amount", - "categories", - "is_categorized", - "suggested_retrieval" -]New value: +[ + "company_id", + "name", + "domain", + "logo_url", + "calendar_year", + "calendar_month", + "period_label", + "tx_count", + "base_total_amount", + "categories", + "is_categorized", + "suggested_categories", + "suggested_retrieval" +] - changed
Output schema / properties / total_count / descriptionPrevious value: -"Matching counterparties BEFORE the row cap, so a capped sweep says what it left out. On the periods scope: DISTINCT counterparties across the window — a multi-month call lists one counterparty on one row per month, so row_count can exceed it."New value: +"Counterparties MATCHING the call, before the row cap, so a capped sweep says what it left out. On the periods scope: the DISTINCT counterparties `rows` names, and a multi-month call lists one counterparty on one row per month, so row_count can exceed it. Under `uncategorized_only` it counts the uncategorized ones alone; the scope's whole population is `categorized_count` plus `uncategorized_count`." - changed
Output schema / properties / uncategorized_count / descriptionPrevious value: -"Rows in this result that carry none."New value: +"COUNTERPARTIES of the scope that carry none, counted the same way. This is the outstanding work; with `categorized_count` it is the scope's whole population." - added
Output schema / properties / uncategorized_onlyAdded value: +{ + "description": "Whether `rows` holds ONLY the counterparties that carry no category. True on the workspace-wide sweep and on a period scope the call filtered. When true, `categorized_count` counts counterparties the result did NOT list.", + "type": "boolean" +}
- Changed
well_list_missing_invoices15 fields changed- added
Output schema / properties / months / items / properties / row_count / descriptionAdded value: +"Counterparties THIS month is missing an invoice from. A counterparty owing an invoice in several months counts in each of them, so these do not sum to the envelope's `row_count`, which counts each counterparty once." - added
Output schema / properties / row_count / descriptionAdded value: +"Rows in `rows`, which is the DISTINCT counterparties the call found. Never the sum of the months' own `row_count`." - changed
Output schema / properties / rows / descriptionPrevious value: -"The card's rows — one per counterparty per month, oldest month first, each tagged with its month."New value: +"The card's rows, ONE per counterparty for the whole call, in the order the oldest month listed them, each naming the months it covers in `months`. A counterparty owing an invoice in several of the months read is one row, never one per month." - removed
Output schema / properties / rows / items / properties / acquisition_statusRemoved value: -{ - "enum": [ - "waiting", - "processing", - "mapped", - "refused" - ], - "type": "string" -} - changed
Output schema / properties / rows / items / properties / base_total_amount / descriptionPrevious value: -"Sum in base_currency; null when an FX rate was missing for any transaction."New value: +"Sum in base_currency across every month in `months`; null when an FX rate was missing for any transaction of any of them." - removed
Output schema / properties / rows / items / properties / calendar_monthRemoved value: -{ - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" -} - removed
Output schema / properties / rows / items / properties / calendar_yearRemoved value: -{ - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" -} - changed
Output schema / properties / rows / items / properties / id / descriptionPrevious value: -"Stable row key: the proof task id when one exists, else the counterparty company id."New value: +"Stable row key: the counterparty company id, or the proof task id of a gap that resolved no company. One key per counterparty for the whole call, so it never repeats across the months the row covers." - added
Output schema / properties / rows / items / properties / monthsAdded value: +{ + "description": "The months of the call this counterparty is missing an invoice in, oldest first: one entry, or several when the same counterparty owes an invoice in more than one of them. The envelope's `periods_covered` names every month READ; this names the months THIS row covers.", + "items": { + "additionalProperties": false, + "properties": { + "acquisition_status": { + "enum": [ + "waiting", + "processing", + "mapped", + "refused" + ], + "type": "string" + }, + "base_total_amount": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "This month's sum in `base_currency`; null when an FX rate was missing for any of its transactions." + }, + "calendar_month": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "calendar_year": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "period_label": { + "description": "The month this row belongs to, e.g. \"June 2026\".", + "type": "string" + }, + "proof_task_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The close-proof task bound to this month's gap; null until one is minted." + }, + "refusal_reason": { + "anyOf": [ + { + "enum": [ + "noop_not_proof", + "noop_no_gap_match", + "noop_ambiguous_gaps", + "noop_task_unavailable", + "warn_bridge_error" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Populated only when THIS month's acquisition_status is refused." + }, + "tx_count": { + "description": "Transactions this counterparty is missing an invoice for in THIS month alone.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "calendar_year", + "calendar_month", + "period_label", + "tx_count", + "base_total_amount", + "proof_task_id", + "acquisition_status", + "refusal_reason" + ], + "type": "object" + }, + "type": "array" +} - removed
Output schema / properties / rows / items / properties / period_labelRemoved value: -{ - "description": "The month this row belongs to, e.g. \"June 2026\".", - "type": "string" -} - removed
Output schema / properties / rows / items / properties / proof_task_idRemoved value: -{ - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "The close-proof task bound to this gap; null until one is minted." -} - removed
Output schema / properties / rows / items / properties / refusal_reasonRemoved value: -{ - "anyOf": [ - { - "enum": [ - "noop_not_proof", - "noop_no_gap_match", - "noop_ambiguous_gaps", - "noop_task_unavailable", - "warn_bridge_error" - ], - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Populated only when acquisition_status is refused." -} - changed
Output schema / properties / rows / items / properties / transactions / descriptionPrevious value: -"The row's own transactions, in ledger order, at most 25 — the same bounded sample the app's card lists under the counterparty."New value: +"The row's own transactions, oldest month first, at most 25 for the WHOLE row: the same bounded sample the app's card lists under the counterparty. A row spanning months carries one sample across them, not one per month." - added
Output schema / properties / rows / items / properties / tx_count / descriptionAdded value: +"Transactions missing an invoice, summed across every month in `months`." - changed
Output schema / properties / rows / items / requiredPrevious value: -[ - "id", - "company_id", - "name", - "logo_url", - "calendar_year", - "calendar_month", - "period_label", - "tx_count", - "transactions", - "transactions_omitted", - "base_total_amount", - "mode", - "available_modes", - "suggested_action", - "matched_provider_name", - "matched_provider_has_blueprint", - "matched_connector_service_id", - "proof_task_id", - "acquisition_status", - "refusal_reason" -]New value: +[ + "id", + "company_id", + "name", + "logo_url", + "months", + "tx_count", + "transactions", + "transactions_omitted", + "base_total_amount", + "mode", + "available_modes", + "suggested_action", + "matched_provider_name", + "matched_provider_has_blueprint", + "matched_connector_service_id" +]
- Changed
well_list_periods1 field changed- changed
Output schema / properties / periods / items / properties / inspectable / descriptionPrevious value: -"The month can be LOOKED INTO. A reader can open its transactions, missing invoices and days. True for the month in progress; every selectable month is also inspectable."New value: +"The month can be LOOKED INTO. A reader can open its transactions, missing invoices and days. True for every month that has begun, the month in progress included, whatever its close verdict and however little it holds. False only for a month that has not begun. Every selectable month is also inspectable."
- Changed
well_list_workspaces2 fields changed- added
Output schema / properties / workspaces / items / properties / has_bank_transactionsAdded value: +{ + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "description": "Whether a connector the workspace BANKS with has delivered any transaction to it: a bank, a neobank, or a treasury or spend platform whose product is an account. An accounting platform and a payment processor deliver transactions too and do NOT count. A transaction whose source connector is unknown, whose install has since been disconnected, or whose catalog entry has been retired does not count either. Only true shows a bank has fed this workspace. false means no such transaction was found; null means the signal could not be read. An absent value is not a zero, and neither false nor null licenses skipping a bank-connection step." +} - changed
Output schema / properties / workspaces / items / requiredPrevious value: -[ - "workspace_id", - "workspace_name", - "is_primary", - "identity" -]New value: +[ + "workspace_id", + "workspace_name", + "is_primary", + "has_bank_transactions", + "identity" +]
- Changed
well_preview_invoice_fetch12 fields changed- removed
Output schema / properties / agentsRemoved value: -{ - "description": "One entry per invoice agent the whole window would launch — one agent run covers one portal, however many months it spans.", - "items": { - "additionalProperties": false, - "properties": { - "base_total_amount": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "Sum in base_currency; null when an FX rate was missing for any transaction in the sum." - }, - "connect_routed_counterparties": { - "description": "How many of `counterparties` the preview suggests connecting instead. They are counted in `tx_count` and `base_total_amount` — the agent would fetch them — and they are ALSO in `connect_rows`, where they are counted as connect counterparties.", - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" - }, - "counterparties": { - "description": "The counterparties this one agent would cover, each tagged with the month it belongs to and with the route the preview suggests for it.", - "items": { - "additionalProperties": false, - "properties": { - "base_total_amount": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "Sum in base_currency; null when an FX rate was missing for any transaction in the sum." - }, - "calendar_month": { - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" - }, - "calendar_year": { - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" - }, - "name": { - "type": "string" - }, - "period_label": { - "description": "The month this row belongs to, e.g. \"June 2026\".", - "type": "string" - }, - "suggested_route": { - "description": "\"agent\" when the agent run is the suggested route. \"connect\" when a Well connector is suggested instead — the counterparty is ALSO in `connect_rows`, and it is listed here so the agent run stays available.", - "enum": [ - "agent", - "connect" - ], - "type": "string" - }, - "tx_count": { - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" - } - }, - "required": [ - "name", - "calendar_year", - "calendar_month", - "period_label", - "tx_count", - "base_total_amount", - "suggested_route" - ], - "type": "object" - }, - "type": "array" - }, - "domain": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "The provider's bare host, e.g. \"aws.amazon.com\". Null when unmatched or the catalog carries no host." - }, - "logo_url": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "The provider's logo; null when unmatched." - }, - "provider_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "provider_name": { - "description": "The supplier portal an agent would run. \"unknown\" when unmatched.", - "type": "string" - }, - "tx_count": { - "description": "Transactions this agent run would fetch, over every counterparty listed above.", - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" - }, - "url": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "The provider's portal address as the catalog holds it, e.g. \"https://members.wework.com/invoices\". `domain` is this address reduced to its host. Null when unmatched or the catalog carries no address. Display only — it labels the portal and never selects it." - } - }, - "required": [ - "provider_name", - "provider_id", - "domain", - "url", - "logo_url", - "counterparties", - "connect_routed_counterparties", - "tx_count", - "base_total_amount" - ], - "type": "object" - }, - "type": "array" -} - changed
Output schema / properties / collect_url / descriptionPrevious value: -"The `/collect` entry that hands these portals to the Well browser extension, naming each one by its `provider_id` and naming this workspace as the link's authorization scope — the page starts nothing until the reader is signed in to Well as a member of it. Null when no agent here carries a provider id, so no portal can be addressed. Opening it starts nothing on its own: the user acts on the page."New value: +"The `/collect` entry that hands these vendors to the Well browser extension, naming each one by its `provider_id` and naming this workspace as the link's authorization scope. The page starts nothing until the reader is signed in to Well as a member of it. It carries every vendor that has an address, whatever that address's `url_source`, because what a vendor's invoices need once the page opens is the app's and the extension's decision rather than this read's. Null in three unrelated cases: no vendor of the window carries an address, none of the addressed vendors carries an id the link can address, or this read could not name the workspace the link authorizes. The hints name which one, and only the first is a verdict on the vendors. Opening it starts nothing on its own: the user acts on the page." - added
Output schema / properties / collect_url_unaddressableAdded value: +{ + "description": "The vendors `collect_url` does not name: the vendor carries no address at all, or it carries one but no catalog id the link can address. A missing published flow is NOT among the reasons, because the link takes an enrichment address exactly like a blueprint one. Present only when the window holds some. They are real gaps and they are listed in `vendors`; report them as vendors the link cannot carry, never as absent.", + "items": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" +} - changed
Output schema / properties / counts / descriptionPrevious value: -"The whole window's counts. `agents` counts the distinct portals across it, so it is never the sum of the months' own `agents`. `upload` and `connect` add each month's counterparty rows, so a counterparty missing an invoice in two of the months read counts once per month."New value: +"The whole window's counts. `vendors` and `agents` count the distinct portals across it, so neither is the sum of the months' own. `upload` and `connect` add each month's counterparty rows, so a counterparty missing an invoice in two of the months read counts once per month." - changed
Output schema / properties / counts / properties / agents / descriptionPrevious value: -"Portals an agent run is available for — one per distinct provider, the connector-covered ones included."New value: +"Portals Well holds a published flow for, one per distinct provider, the connector-covered ones included. It marks the shorter route and is NOT the link's own set: `collect_url` names every addressed vendor it can name by id, so it routinely carries portals this count leaves out." - added
Output schema / properties / counts / properties / vendorsAdded value: +{ + "description": "Vendors the card lists — one per distinct portal, or per counterparty where none matched.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" +} - changed
Output schema / properties / counts / requiredPrevious value: -[ - "agents", - "agent_tx", - "upload", - "connect" -]New value: +[ + "vendors", + "agents", + "agent_tx", + "upload", + "connect" +] - changed
Output schema / properties / months / items / properties / counts / properties / agents / descriptionPrevious value: -"Portals an agent run is available for — one per distinct provider, the connector-covered ones included."New value: +"Portals Well holds a published flow for, one per distinct provider, the connector-covered ones included. It marks the shorter route and is NOT the link's own set: `collect_url` names every addressed vendor it can name by id, so it routinely carries portals this count leaves out." - added
Output schema / properties / months / items / properties / counts / properties / vendorsAdded value: +{ + "description": "Vendors the card lists — one per distinct portal, or per counterparty where none matched.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" +} - changed
Output schema / properties / months / items / properties / counts / requiredPrevious value: -[ - "agents", - "agent_tx", - "upload", - "connect" -]New value: +[ + "vendors", + "agents", + "agent_tx", + "upload", + "connect" +] - added
Output schema / properties / vendorsAdded value: +{ + "description": "EVERY vendor of the rows THIS CALL covers, whatever route its invoice would arrive by — one entry per supplier portal across the whole window, or per counterparty where no portal matched. The ROUTE never filters this list: a vendor Well has no published flow and no connector for is listed exactly like the rest. What the call covers can still be narrower than the period, and the envelope says so: when `scoped_to_selected_counterparties` is present these are the picked counterparties alone and `selection_scope` sizes the remainder, and a `hints` line names any group the projection could produce no vendor for.", + "items": { + "additionalProperties": false, + "properties": { + "base_total_amount": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Sum in base_currency; null when an FX rate was missing for any transaction in the sum." + }, + "connect_routed_counterparties": { + "description": "How many of `counterparties` the preview suggests connecting instead. They are counted in `tx_count` and `base_total_amount`, and they are ALSO in `connect_rows`, where they are counted as connect counterparties.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "counterparties": { + "description": "The counterparties this one vendor covers, each tagged with the month it belongs to and with the route the preview suggests for it.", + "items": { + "additionalProperties": false, + "properties": { + "base_total_amount": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Sum in base_currency; null when an FX rate was missing for any transaction in the sum." + }, + "calendar_month": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "calendar_year": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "name": { + "type": "string" + }, + "period_label": { + "description": "The month this row belongs to, e.g. \"June 2026\".", + "type": "string" + }, + "suggested_route": { + "description": "\"agent\" when the agent run is the suggested route. \"connect\" when a Well connector is suggested instead — the counterparty is ALSO in `connect_rows`. \"upload\" when Well holds neither a connector nor a published flow for it — the counterparty is ALSO in `upload_rows`.", + "enum": [ + "agent", + "connect", + "upload" + ], + "type": "string" + }, + "tx_count": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "name", + "calendar_year", + "calendar_month", + "period_label", + "tx_count", + "base_total_amount", + "suggested_route" + ], + "type": "object" + }, + "type": "array" + }, + "domain": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The vendor's own bare host, e.g. \"aws.amazon.com\" — the catalog provider's host, or the counterparty company's. It identifies the vendor and its mark; it is NOT `url` reduced, because `url` may point deeper into the portal." + }, + "key": { + "description": "Stable identity for this entry across a re-read: the portal when one matched, else the counterparty row. Two vendors sharing a display name have different keys, so key rows on this and never on `name`.", + "type": "string" + }, + "logo_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The vendor's mark; null when neither the catalog nor its host resolved one." + }, + "name": { + "description": "The vendor as the card names it: the matched provider's name, or the counterparty's own when none matched.", + "type": "string" + }, + "provider_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The catalog id, and the only field that can put this vendor on `collect_url`. Necessary but NOT sufficient: the link also needs the vendor to carry a `url`, and the vendor has to fit the link's own ceiling. Null when no provider matched. A vendor off the link is still listed and is still offered for the pick, so report it as one the link cannot carry, never as one Well leaves out." + }, + "tx_count": { + "description": "Transactions still missing an invoice, over every counterparty listed above.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Where this vendor's invoices are, e.g. \"https://www.dropbox.com/manage/billing\". `url_source` says how close that address stands to the invoices. Null only when `url_source` is \"none\". An address is what makes a vendor offerable at all: a vendor carrying one is offered for the pick, and with a `provider_id` it is also named on `collect_url`, whatever its `url_source`. The address itself never travels, because the id is the only field the extension acts on." + }, + "url_source": { + "description": "Where `url` came from. \"blueprint\" — the provider's published flow, so it opens the billing page itself. \"enrichment\" — the catalog's entry address or the company's own domain, so it is the vendor's front door and the user still has to find the invoices on it. \"none\" — no address at all, and `url` is null. It says where the address came from and gates nothing: an \"enrichment\" vendor is offered, and carried on the link, exactly like a \"blueprint\" one.", + "enum": [ + "blueprint", + "enrichment", + "none" + ], + "type": "string" + } + }, + "required": [ + "key", + "name", + "provider_id", + "domain", + "url", + "url_source", + "logo_url", + "counterparties", + "connect_routed_counterparties", + "tx_count", + "base_total_amount" + ], + "type": "object" + }, + "type": "array" +} - changed
Output schema / requiredPrevious value: -[ - "agents", - "upload_rows", - "connect_rows", - "success" -]New value: +[ + "vendors", + "upload_rows", + "connect_rows", + "success" +]
- Added
well_upload_statement_bytes - Added
well_upload_statement_content
8 tool updates
- Changed
well_get_burn2 fields changed- added
Output schema / properties / hints / items / properties / cta_linkAdded value: +{ + "format": "uri", + "type": "string" +} - added
Output schema / properties / hints / items / properties / signal_idAdded value: +{ + "type": "string" +}
- Changed
well_get_cash_flow_bridge2 fields changed- added
Output schema / properties / hints / items / properties / cta_linkAdded value: +{ + "format": "uri", + "type": "string" +} - added
Output schema / properties / hints / items / properties / signal_idAdded value: +{ + "type": "string" +}
- Changed
well_get_cash_forecast2 fields changed- added
Output schema / properties / hints / items / properties / cta_linkAdded value: +{ + "format": "uri", + "type": "string" +} - added
Output schema / properties / hints / items / properties / signal_idAdded value: +{ + "type": "string" +}
- Changed
well_get_cash_position2 fields changed- added
Output schema / properties / hints / items / properties / cta_linkAdded value: +{ + "format": "uri", + "type": "string" +} - added
Output schema / properties / hints / items / properties / signal_idAdded value: +{ + "type": "string" +}
- Changed
well_get_cost_structure2 fields changed- added
Output schema / properties / hints / items / properties / cta_linkAdded value: +{ + "format": "uri", + "type": "string" +} - added
Output schema / properties / hints / items / properties / signal_idAdded value: +{ + "type": "string" +}
- Changed
well_get_runway2 fields changed- added
Output schema / properties / hints / items / properties / cta_linkAdded value: +{ + "format": "uri", + "type": "string" +} - added
Output schema / properties / hints / items / properties / signal_idAdded value: +{ + "type": "string" +}
- Changed
well_get_statement_import_result14 fields changed- added
Output schema / properties / records / items / properties / amount / anyOfAdded value: +[ + { + "type": "number" + }, + { + "type": "null" + } +] - removed
Output schema / properties / records / items / properties / amount / typeRemoved value: -"number" - added
Output schema / properties / records / items / properties / balanceAtFromAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] +} - added
Output schema / properties / records / items / properties / categoryAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] +} - added
Output schema / properties / records / items / properties / closingBookedAdded value: +{ + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] +} - added
Output schema / properties / records / items / properties / currency / anyOfAdded value: +[ + { + "type": "string" + }, + { + "type": "null" + } +] - removed
Output schema / properties / records / items / properties / currency / typeRemoved value: -"string" - added
Output schema / properties / records / items / properties / domainAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] +} - added
Output schema / properties / records / items / properties / openingBookedAdded value: +{ + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] +} - added
Output schema / properties / records / items / properties / primaryBadgeAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] +} - added
Output schema / properties / records / items / properties / receiverAdded value: +{ + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "logo_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + } + }, + "required": [ + "name", + "logo_url" + ], + "type": "object" + }, + { + "type": "null" + } + ] +} - added
Output schema / properties / records / items / properties / rootAdded value: +{ + "enum": [ + "companies", + "people", + "invoices", + "documents", + "transactions", + "accounts", + "connectors", + "workspace_connectors", + "ledger_accounts", + "journals", + "journal_entries", + "tax_rates", + "invoice_transactions", + "memberships", + "payment_means", + "media", + "emails", + "phones", + "web_links", + "locations", + "categories", + "invoice_items", + "account_balances", + "exchange_rates", + "cards", + "checks", + "invoice_payment_means", + "workspaces", + "blueprint_runs", + "chat_conversations", + "tasks", + "billing_events" + ], + "type": "string" +} - added
Output schema / properties / records / items / properties / secondaryBadgeAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] +} - changed
Output schema / properties / records / items / requiredPrevious value: -[ - "id", - "date", - "label", - "amount", - "currency", - "counterparty" -]New value: +[ + "root", + "id", + "date", + "label", + "amount", + "currency", + "counterparty", + "receiver", + "category", + "primaryBadge", + "secondaryBadge", + "openingBooked", + "closingBooked", + "balanceAtFrom", + "domain" +]
- Changed
well_list_periods4 fields changed- added
Output schema / properties / periods / items / properties / bank_transaction_countAdded value: +{ + "description": "The transaction_count subset delivered by a connector the workspace BANKS with, meaning its category is `banks` or its service id is on the bank-account list. An accounting platform and a payment processor deliver transactions too and are not counted. A transaction whose source connector is unknown, or since disconnected, is not counted either, so a 0 never licenses skipping a bank-connection step.", + "type": "number" +} - added
Output schema / properties / periods / items / properties / inspectableAdded value: +{ + "description": "The month can be LOOKED INTO. A reader can open its transactions, missing invoices and days. True for the month in progress; every selectable month is also inspectable.", + "type": "boolean" +} - added
Output schema / properties / periods / items / properties / selectable / descriptionAdded value: +"The month can be CLOSED. False while it is still running." - changed
Output schema / properties / periods / items / requiredPrevious value: -[ - "calendar_year", - "calendar_month", - "fiscal_year", - "fiscal_period", - "label", - "is_complete", - "selectable", - "close_status", - "close_reason", - "invoice_state", - "transaction_count", - "unposted_invoice_count", - "uncategorized_transactions", - "categorized_unposted_transactions", - "missing_invoice_count", - "days" -]New value: +[ + "calendar_year", + "calendar_month", + "fiscal_year", + "fiscal_period", + "label", + "is_complete", + "selectable", + "inspectable", + "close_status", + "close_reason", + "invoice_state", + "transaction_count", + "bank_transaction_count", + "unposted_invoice_count", + "uncategorized_transactions", + "categorized_unposted_transactions", + "missing_invoice_count", + "days" +]
2 tool updates
- Changed
well_create_statement_upload1 field changed- added
Output schema / properties / document_idAdded value: +{ + "description": "The pre-allocated document id — poll well_get_statement_import_result with it.", + "type": "string" +}
- Added
well_get_statement_import_result
40 tool updates
- Changed
well_add_contact_channel1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_create_company1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_create_invoice_document1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_create_invoice_from_data1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_create_person1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_create_statement_upload1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_delete_company1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_delete_invoice1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_delete_person1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_get_burn6 fields changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which." - added
Output schema / properties / baselineAdded value: +{ + "additionalProperties": false, + "description": "What `change` was measured against. It arrives with `change`, `trend` and `trend_polarity` as one group; when the baseline has no named period all four are withheld rather than sending a percentage with no referent.", + "properties": { + "basis": { + "const": "previous_trailing_window", + "description": "The baseline is a trailing average over the months in `period`, not one month's reading.", + "type": "string" + }, + "period": { + "additionalProperties": false, + "description": "Inclusive `YYYY-MM` bounds of the months the baseline actually measured.", + "properties": { + "end": { + "type": "string" + }, + "start": { + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + "value": { + "description": "The average burn over `period` — the figure `change` compares `amount` against.", + "type": "number" + } + }, + "required": [ + "value", + "basis", + "period" + ], + "type": "object" +} - added
Output schema / properties / change / descriptionAdded value: +"Signed percentage change of `amount` against `baseline.value` (e.g. -20.7 for a 20.7% fall). Measured over `baseline.period`, never over the months in `per_month` — the baseline is a different window, so a delta computed from the series is a different number." - added
Output schema / properties / resolved_workspaceAdded value: +{ + "additionalProperties": false, + "description": "The workspace that answered, when the caller named none and the token authorizes several.", + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "name" + ], + "type": "object" +} - added
Output schema / properties / trend / descriptionAdded value: +"Whether the change is GOOD for this KPI, not the direction the number moved. Burn is lower-is-better, so \"up\" means burn FELL and \"down\" means it ROSE. Read the sign of `change` for direction." - added
Output schema / properties / trend_polarityAdded value: +{ + "const": "lower_is_better", + "description": "Which direction counts as good. Burn is lower-is-better, which is why a fall reads as `trend: \"up\"`.", + "type": "string" +}
- Changed
well_get_cash_flow_bridge5 fields changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which." - added
Output schema / properties / period_endAdded value: +{ + "description": "Inclusive YYYY-MM-DD end of that window.", + "type": "string" +} - added
Output schema / properties / period_startAdded value: +{ + "description": "Inclusive YYYY-MM-DD start of the window the flow bars cover (a trailing multi-month span).", + "type": "string" +} - added
Output schema / properties / resolved_workspaceAdded value: +{ + "additionalProperties": false, + "description": "The workspace that answered, when the caller named none and the token authorizes several.", + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "name" + ], + "type": "object" +} - changed
Output schema / properties / steps / items / properties / kind / enumPrevious value: -[ - "start", - "increase", - "decrease", - "total" -]New value: +[ + "start", + "increase", + "decrease", + "total", + "unexplained" +]
- Changed
well_get_cash_forecast3 fields changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which." - added
Output schema / properties / anchorAdded value: +{ + "additionalProperties": false, + "properties": { + "amount": { + "type": "number" + }, + "basis": { + "enum": [ + "closed_month_end", + "current_position" + ], + "type": "string" + }, + "month": { + "type": "string" + } + }, + "required": [ + "month", + "amount", + "basis" + ], + "type": "object" +} - added
Output schema / properties / resolved_workspaceAdded value: +{ + "additionalProperties": false, + "description": "The workspace that answered, when the caller named none and the token authorizes several.", + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "name" + ], + "type": "object" +}
- Changed
well_get_cash_position2 fields changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which." - added
Output schema / properties / resolved_workspaceAdded value: +{ + "additionalProperties": false, + "description": "The workspace that answered, when the caller named none and the token authorizes several.", + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "name" + ], + "type": "object" +}
- Changed
well_get_cost_structure3 fields changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which." - added
Output schema / properties / label_provenanceAdded value: +{ + "description": "Whether a human owns the labels: every one set or confirmed by a person (curated), none confirmed (machine), some of each (mixed), or not grouped on a category at all (unlabelled).", + "enum": [ + "curated", + "machine", + "mixed", + "unlabelled" + ], + "type": "string" +} - added
Output schema / properties / resolved_workspaceAdded value: +{ + "additionalProperties": false, + "description": "The workspace that answered, when the caller named none and the token authorizes several.", + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "name" + ], + "type": "object" +}
- Changed
well_get_design_tokens1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer."
- Changed
well_get_entity2 fields changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did." - added
Output schema / properties / resolved_workspaceAdded value: +{ + "additionalProperties": false, + "description": "The workspace that answered, when the caller named none and the token authorizes several.", + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "name" + ], + "type": "object" +}
- Changed
well_get_investment_holdings2 fields changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did." - added
Output schema / properties / resolved_workspaceAdded value: +{ + "additionalProperties": false, + "description": "The workspace that answered, when the caller named none and the token authorizes several.", + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "name" + ], + "type": "object" +}
- Changed
well_get_own_company2 fields changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did." - added
Output schema / properties / resolved_workspaceAdded value: +{ + "additionalProperties": false, + "description": "The workspace that answered, when the caller named none and the token authorizes several.", + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "name" + ], + "type": "object" +}
- Changed
well_get_runway7 fields changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which." - added
Output schema / properties / baselineAdded value: +{ + "additionalProperties": false, + "description": "What `change` was measured against. It arrives with `change`, `trend` and `trend_polarity` as one group; when the baseline has no named period all four are withheld rather than sending a percentage with no referent.", + "properties": { + "basis": { + "const": "previous_month_end", + "description": "`period` names the cash month-end the baseline was anchored at, not one instant when every input was read.", + "type": "string" + }, + "burn_window": { + "additionalProperties": false, + "description": "Inclusive `YYYY-MM` bounds of the trailing burn the baseline divided by. It normally ends before `period`, which is why the baseline is not a single-day reading.", + "properties": { + "end": { + "type": "string" + }, + "start": { + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + "period": { + "additionalProperties": false, + "description": "The baseline's cash month-end as `YYYY-MM`. Both bounds hold that one month.", + "properties": { + "end": { + "type": "string" + }, + "start": { + "type": "string" + } + }, + "required": [ + "start", + "end" + ], + "type": "object" + }, + "value": { + "description": "Months of runway in the baseline — the figure `change` compares `months` against.", + "type": "number" + } + }, + "required": [ + "value", + "basis", + "period" + ], + "type": "object" +} - added
Output schema / properties / change / descriptionAdded value: +"Signed percentage change of `months` against `baseline.value`." - removed
Output schema / properties / forecastRemoved value: -{ - "description": "The deterministic cash-position series behind the runway, oldest first: settled month-end balances up to the latest closed month, then the worst-case projection. At most one of `actuals`/`projection` carries a number per entry; both null is a month with no reading, which stays on the axis as a gap rather than a zero. Draw this rather than extrapolating cash minus burn — that arithmetic ignores the excluded accounts, the FX gaps and the closed-month anchoring the service applies.", - "items": { - "additionalProperties": false, - "properties": { - "actuals": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "Settled month-end cash. Null on projection months." - }, - "month": { - "description": "UTC YYYY-MM.", - "type": "string" - }, - "projection": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "Worst-case projected month-end cash, no revenue assumed. Null on past months." - } - }, - "required": [ - "month", - "actuals", - "projection" - ], - "type": "object" - }, - "type": "array" -} - added
Output schema / properties / resolved_workspaceAdded value: +{ + "additionalProperties": false, + "description": "The workspace that answered, when the caller named none and the token authorizes several.", + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "name" + ], + "type": "object" +} - added
Output schema / properties / trend / descriptionAdded value: +"Whether the change is GOOD for this KPI, not the direction the number moved. Runway is higher-is-better, so \"up\" means the runway grew. Read the sign of `change` for direction." - added
Output schema / properties / trend_polarityAdded value: +{ + "const": "higher_is_better", + "description": "Which direction counts as good. Runway is higher-is-better, so a longer runway reads as `trend: \"up\"`.", + "type": "string" +}
- Changed
well_get_schema1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer."
- Changed
well_invoke_connector_tool1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_list_connector_tools2 fields changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did." - added
Output schema / properties / resolved_workspaceAdded value: +{ + "additionalProperties": false, + "description": "The workspace that answered, when the caller named none and the token authorizes several.", + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "name" + ], + "type": "object" +}
- Changed
well_list_connectors7 fields changed- added
Input schema / properties / subtitleAdded value: +{ + "description": "Supporting line under the connect card's heading, OVERRIDING the scope-derived subtitle. At most 240 characters. Omit to keep the default wording for the requested kind.", + "maxLength": 240, + "type": "string" +} - added
Input schema / properties / titleAdded value: +{ + "description": "Heading for the connect card shown to the user, OVERRIDING the wording the card otherwise derives from the scope/kind. Use it to frame the step in its flow (e.g. \"Connect your accounting tool for the close\"). At most 120 characters. Omit to keep the default wording for the requested kind.", + "maxLength": 120, + "type": "string" +} - changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did." - added
Output schema / properties / install_all_omittedAdded value: +{ + "description": "The service ids install_all_url could not carry, because one link names a bounded number of connectors. Offer these rows their own install_url instead of promising the batch link covers them.", + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / install_all_urlAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "One link that installs every installable connector in this result. Null when the result offers nothing to install, or when its scope names a set the reader has not chosen. When it is non-null it is the ONLY install link the answer offers — do not list the rows' own install_url beside it. When it is null, the rows' own install_url is the offer instead." +} - added
Output schema / properties / resolved_workspaceAdded value: +{ + "additionalProperties": false, + "description": "The workspace that answered, when the caller named none and the token authorizes several.", + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "name" + ], + "type": "object" +} - changed
Output schema / requiredPrevious value: -[ - "success" -]New value: +[ + "install_all_url", + "install_all_omitted", + "success" +]
- Changed
well_list_counterparties5 fields changed- added
Input schema / properties / cursorAdded value: +{ + "description": "The next page of the uncategorized sweep: pass back the `next_cursor` the previous call returned. Only the sweep pages, so this needs `uncategorized_only: true`.", + "type": "string" +} - changed
Input schema / properties / uncategorized_only / descriptionPrevious value: -"Instead of a period, sweep the WHOLE workspace for counterparties that carry no industry category. Returns at most 50 rows plus the total."New value: +"Instead of a period, sweep the WHOLE workspace for counterparties that carry no industry category. Returns 50 rows per page plus the total, and a `next_cursor` for the page after this one." - changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did." - added
Output schema / properties / next_cursorAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The uncategorized sweep's next page: pass it back as `cursor`. Null when this page ends the sweep, absent on the periods scope, which pages nothing." +} - added
Output schema / properties / resolved_workspaceAdded value: +{ + "additionalProperties": false, + "description": "The workspace that answered, when the caller named none and the token authorizes several.", + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "name" + ], + "type": "object" +}
- Changed
well_list_missing_invoices2 fields changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did." - added
Output schema / properties / resolved_workspaceAdded value: +{ + "additionalProperties": false, + "description": "The workspace that answered, when the caller named none and the token authorizes several.", + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "name" + ], + "type": "object" +}
- Changed
well_list_periods11 fields changed- changed
Input schema / properties / months / descriptionPrevious value: -"How many recent calendar months to return, ending with the current month. Each month holding activity costs one extra read for its invoice state."New value: +"How many recent calendar months to return, ending with the current month. Each month holding activity costs one extra read for its invoice state. Ignored when `year` is given." - added
Input schema / properties / purposeAdded value: +{ + "description": "Why the months are being listed, set by the calling skill (not guessed from the user's words). \"close\" is book closure: the card paints close readiness, and each month carries its per-day `close_days` and the counts behind its \"why not ready\" errands. Omit or \"collect\" for invoice retrieval (the default), which paints the retrieval axis and skips the close-readiness fields.", + "enum": [ + "close", + "collect" + ], + "type": "string" +} - changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did." - added
Input schema / properties / yearAdded value: +{ + "description": "One calendar year to return in full — all twelve of its months, December back to January, instead of the recent window. Use it to reach a year the recent window does not cover, in either direction; future months come back visible but never selectable. Accepted range: 2000-2100, also reported as `navigable_years`.", + "maximum": 2100, + "minimum": 2000, + "type": "integer" +} - added
Output schema / properties / navigable_yearsAdded value: +{ + "additionalProperties": false, + "description": "The calendar years a `year` request is answered for. A picker's year steppers stop here.", + "properties": { + "earliest": { + "type": "number" + }, + "latest": { + "type": "number" + } + }, + "required": [ + "earliest", + "latest" + ], + "type": "object" +} - added
Output schema / properties / periods / items / properties / categorized_unposted_transactionsAdded value: +{ + "description": "Categorized transactions not yet posted to the ledger — part of the 'review and book' close errand.", + "type": "number" +} - added
Output schema / properties / periods / items / properties / close_daysAdded value: +{ + "description": "Days of the month carrying a non-neutral close-readiness state, ascending. Present ONLY for a \"close\" call; a day absent from it is \"neutral\" (nothing to close).", + "items": { + "additionalProperties": false, + "properties": { + "day": { + "maximum": 31, + "minimum": 1, + "type": "integer" + }, + "state": { + "enum": [ + "posted", + "progress", + "neutral" + ], + "type": "string" + } + }, + "required": [ + "day", + "state" + ], + "type": "object" + }, + "type": "array" +} - added
Output schema / properties / periods / items / properties / uncategorized_transactionsAdded value: +{ + "description": "Transactions in the month not yet categorized — the 'help categorize' close errand.", + "type": "number" +} - changed
Output schema / properties / periods / items / requiredPrevious value: -[ - "calendar_year", - "calendar_month", - "fiscal_year", - "fiscal_period", - "label", - "is_complete", - "selectable", - "close_status", - "close_reason", - "invoice_state", - "transaction_count", - "unposted_invoice_count", - "missing_invoice_count", - "days" -]New value: +[ + "calendar_year", + "calendar_month", + "fiscal_year", + "fiscal_period", + "label", + "is_complete", + "selectable", + "close_status", + "close_reason", + "invoice_state", + "transaction_count", + "unposted_invoice_count", + "uncategorized_transactions", + "categorized_unposted_transactions", + "missing_invoice_count", + "days" +] - added
Output schema / properties / resolved_workspaceAdded value: +{ + "additionalProperties": false, + "description": "The workspace that answered, when the caller named none and the token authorizes several.", + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "name" + ], + "type": "object" +} - changed
Output schema / requiredPrevious value: -[ - "workspace_id", - "fiscal_year_start_month", - "base_currency", - "periods", - "default_period", - "success" -]New value: +[ + "workspace_id", + "fiscal_year_start_month", + "base_currency", + "periods", + "default_period", + "navigable_years", + "success" +]
- Changed
well_list_workspaces1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Optional: this tool describes the token itself rather than one workspace's data, so omitting it returns the same answer."
- Changed
well_preview_invoice_fetch2 fields changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did." - added
Output schema / properties / resolved_workspaceAdded value: +{ + "additionalProperties": false, + "description": "The workspace that answered, when the caller named none and the token authorizes several.", + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "name" + ], + "type": "object" +}
- Changed
well_query_records1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit to query every authorized workspace at once; each row comes back tagged with the workspace it belongs to."
- Changed
well_remove_contact_channel1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_resolve_reconciliation_task1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_resolve_register_diff_gap1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_run_register_diff1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_set_own_company1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_switch_workspace1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_update_company1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_update_invoice1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_update_person1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_upsert_accounting_settings1 field changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which."
- Changed
well_wait_for_selection2 fields changed- changed
Input schema / properties / workspace_id / descriptionPrevious value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did." - added
Output schema / properties / resolved_workspaceAdded value: +{ + "additionalProperties": false, + "description": "The workspace that answered, when the caller named none and the token authorizes several.", + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "workspace_id", + "name" + ], + "type": "object" +}
9 tool updates
- Added
well_create_statement_upload - Added
well_get_burn - Added
well_get_cash_flow_bridge - Added
well_get_cash_forecast - Changed
well_get_cash_position2 fields changed- added
Input schema / properties / monthAdded value: +{ + "description": "Calendar month of the reporting period, 1 = January … 12 = December. Must be given together with `year`. Omit both to read the live/current figure.", + "maximum": 12, + "minimum": 1, + "type": "integer" +} - added
Input schema / properties / yearAdded value: +{ + "description": "Calendar year of the reporting period, e.g. 2026. Must be given together with `month`.", + "maximum": 2100, + "minimum": 2000, + "type": "integer" +}
- Changed
well_get_cost_structure2 fields changed- added
Input schema / properties / monthAdded value: +{ + "description": "Calendar month of the reporting period, 1 = January … 12 = December. Must be given together with `year`. Omit both to read the live/current figure.", + "maximum": 12, + "minimum": 1, + "type": "integer" +} - added
Input schema / properties / yearAdded value: +{ + "description": "Calendar year of the reporting period, e.g. 2026. Must be given together with `month`.", + "maximum": 2100, + "minimum": 2000, + "type": "integer" +}
- Changed
well_get_runway4 fields changed- added
Input schema / properties / monthAdded value: +{ + "description": "Calendar month of the reporting period, 1 = January … 12 = December. Must be given together with `year`. Omit both to read the live/current figure.", + "maximum": 12, + "minimum": 1, + "type": "integer" +} - added
Input schema / properties / yearAdded value: +{ + "description": "Calendar year of the reporting period, e.g. 2026. Must be given together with `month`.", + "maximum": 2100, + "minimum": 2000, + "type": "integer" +} - added
Output schema / properties / changeAdded value: +{ + "type": "number" +} - added
Output schema / properties / trendAdded value: +{ + "enum": [ + "up", + "down", + "neutral" + ], + "type": "string" +}
- Changed
well_preview_invoice_fetch2 fields changed- added
Output schema / properties / scoped_to_selected_counterpartiesAdded value: +{ + "const": true, + "description": "Present when a counterparty pick narrowed this preview: for the months the pick was made against, every route below covers only those companies. A month outside the pick is covered in full.", + "type": "boolean" +} - added
Output schema / properties / selection_scopeAdded value: +{ + "additionalProperties": false, + "description": "What the pick removed. Present with `scoped_to_selected_counterparties`, so the size of the truncation is readable beside the result.", + "properties": { + "row_count_before_filter": { + "description": "Counterparty rows the months read hold in total, before the pick narrowed them. Includes the months the pick does not bound, which are reported in full.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "rows_dropped_by_filter": { + "description": "How many of those rows the pick left out — gaps the routes and the counts below do not cover.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "row_count_before_filter", + "rows_dropped_by_filter" + ], + "type": "object" +}
- Added
well_upsert_accounting_settings
4 tool updates
- Changed
well_get_runway1 field changed- added
Output schema / properties / forecastAdded value: +{ + "description": "The deterministic cash-position series behind the runway, oldest first: settled month-end balances up to the latest closed month, then the worst-case projection. At most one of `actuals`/`projection` carries a number per entry; both null is a month with no reading, which stays on the axis as a gap rather than a zero. Draw this rather than extrapolating cash minus burn — that arithmetic ignores the excluded accounts, the FX gaps and the closed-month anchoring the service applies.", + "items": { + "additionalProperties": false, + "properties": { + "actuals": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Settled month-end cash. Null on projection months." + }, + "month": { + "description": "UTC YYYY-MM.", + "type": "string" + }, + "projection": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Worst-case projected month-end cash, no revenue assumed. Null on past months." + } + }, + "required": [ + "month", + "actuals", + "projection" + ], + "type": "object" + }, + "type": "array" +}
- Changed
well_list_connectors2 fields changed- added
Input schema / properties / from_selectionAdded value: +{ + "const": true, + "description": "Scope the card to the connectors behind the counterparties the user picked on the missing-invoices card this session, each installable one pre-checked. Use it for the connect step that FOLLOWS a vendor pick, where the user has already chosen and the card must offer only what they chose. Cannot be combined with `q` or `kind` — those name a catalog to browse, and this names a set already decided. Returns an empty list when the session holds no pick for this workspace, or when no picked counterparty matched a connector.", + "type": "boolean" +} - added
Output schema / properties / scopeAdded value: +{ + "description": "What this result is a list OF: the requested kind, the picked vendors' connectors, or the curated catalog when neither was asked for. The card words itself from this, so it never describes the rows as a domain that was not requested.", + "enum": [ + "catalog", + "bank", + "accounting", + "invoicing", + "picked_vendors" + ], + "type": "string" +}
- Changed
well_list_missing_invoices11 fields changed- changed
Output schema / properties / dropped_groups / properties / bank_internal / descriptionPrevious value: -"Party-less bank operations — nothing to collect."New value: +"GROUPS of party-less bank operations — nothing to collect. One group per month that held any, NOT a count of operations: never quote this number as a quantity of bank operations." - changed
Output schema / properties / dropped_groups / properties / unknown / descriptionPrevious value: -"Spend with no resolved counterparty."New value: +"GROUPS of spend with no resolved counterparty. One group per month that held any, NOT a count of transactions or of counterparties." - changed
Output schema / properties / dropped_groups / properties / unnamed_company / descriptionPrevious value: -"Counterparties the card cannot render (no name or no key)."New value: +"GROUPS the card cannot render (no name or no key) — one per company, NOT a count of transactions." - changed
Output schema / properties / months / items / properties / dropped_groups / properties / bank_internal / descriptionPrevious value: -"Party-less bank operations — nothing to collect."New value: +"GROUPS of party-less bank operations — nothing to collect. One group per month that held any, NOT a count of operations: never quote this number as a quantity of bank operations." - changed
Output schema / properties / months / items / properties / dropped_groups / properties / unknown / descriptionPrevious value: -"Spend with no resolved counterparty."New value: +"GROUPS of spend with no resolved counterparty. One group per month that held any, NOT a count of transactions or of counterparties." - changed
Output schema / properties / months / items / properties / dropped_groups / properties / unnamed_company / descriptionPrevious value: -"Counterparties the card cannot render (no name or no key)."New value: +"GROUPS the card cannot render (no name or no key) — one per company, NOT a count of transactions." - added
Output schema / properties / rows / items / properties / available_modesAdded value: +{ + "description": "Every collection method this counterparty offers, agent first and upload last — 2 or 3 entries, never one. `agent` and `upload` stand for every row; `connect` joins them only when the catalog holds a connector for the matched provider, which is exactly what a non-null matched_connector_service_id reports. `mode` names the ONE route the card suggests, this names all the routes the user may pick.", + "items": { + "enum": [ + "agent", + "connect", + "upload" + ], + "type": "string" + }, + "type": "array" +} - changed
Output schema / properties / rows / items / properties / logo_url / descriptionPrevious value: -"The matched provider's logo; null when unmatched."New value: +"The counterparty's mark, resolved in that order: the matched provider's stored logo, else the counterparty company's own stored logo — the one company enrichment resolved from that company's domain — else the mark derived from the matched portal's host. Null when the row matched no portal and its company carries no logo, and the card then draws initials." - added
Output schema / properties / rows / items / properties / transactionsAdded value: +{ + "description": "The row's own transactions, in ledger order, at most 25 — the same bounded sample the app's card lists under the counterparty.", + "items": { + "additionalProperties": false, + "properties": { + "amount": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Signed amount in `currency` — negative when the money leaves the account." + }, + "base_amount": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "The same amount converted to `base_currency`, still signed. Sum the magnitudes of these to reach `base_total_amount`; null when no FX rate covered the date." + }, + "category": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Display label of THIS transaction's management category, not the group's. Null when uncategorized." + }, + "category_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Stable taxonomy key of the same category — branch on this, never on the label. Null when off-catalog." + }, + "currency": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "date": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The day the transaction posts under (YYYY-MM-DD); null when it carries no date." + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The bank's remittance text — what distinguishes one line from the next. Null when the row carries none." + }, + "id": { + "type": "string" + } + }, + "required": [ + "id", + "date", + "description", + "category", + "category_key", + "amount", + "currency", + "base_amount" + ], + "type": "object" + }, + "type": "array" +} - added
Output schema / properties / rows / items / properties / transactions_omittedAdded value: +{ + "description": "How many of the row's transactions the cap left out: `tx_count` minus the listed ones.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" +} - changed
Output schema / properties / rows / items / requiredPrevious value: -[ - "id", - "company_id", - "name", - "logo_url", - "calendar_year", - "calendar_month", - "period_label", - "tx_count", - "base_total_amount", - "mode", - "suggested_action", - "matched_provider_name", - "matched_provider_has_blueprint", - "matched_connector_service_id", - "proof_task_id", - "acquisition_status", - "refusal_reason" -]New value: +[ + "id", + "company_id", + "name", + "logo_url", + "calendar_year", + "calendar_month", + "period_label", + "tx_count", + "transactions", + "transactions_omitted", + "base_total_amount", + "mode", + "available_modes", + "suggested_action", + "matched_provider_name", + "matched_provider_has_blueprint", + "matched_connector_service_id", + "proof_task_id", + "acquisition_status", + "refusal_reason" +]
- Changed
well_preview_invoice_fetch19 fields changed- added
Output schema / properties / agents / items / properties / connect_routed_counterpartiesAdded value: +{ + "description": "How many of `counterparties` the preview suggests connecting instead. They are counted in `tx_count` and `base_total_amount` — the agent would fetch them — and they are ALSO in `connect_rows`, where they are counted as connect counterparties.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" +} - changed
Output schema / properties / agents / items / properties / counterparties / descriptionPrevious value: -"The counterparties this one agent would cover, each tagged with the month it belongs to."New value: +"The counterparties this one agent would cover, each tagged with the month it belongs to and with the route the preview suggests for it." - added
Output schema / properties / agents / items / properties / counterparties / items / properties / suggested_routeAdded value: +{ + "description": "\"agent\" when the agent run is the suggested route. \"connect\" when a Well connector is suggested instead — the counterparty is ALSO in `connect_rows`, and it is listed here so the agent run stays available.", + "enum": [ + "agent", + "connect" + ], + "type": "string" +} - changed
Output schema / properties / agents / items / properties / counterparties / items / requiredPrevious value: -[ - "name", - "calendar_year", - "calendar_month", - "period_label", - "tx_count", - "base_total_amount" -]New value: +[ + "name", + "calendar_year", + "calendar_month", + "period_label", + "tx_count", + "base_total_amount", + "suggested_route" +] - added
Output schema / properties / agents / items / properties / domainAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The provider's bare host, e.g. \"aws.amazon.com\". Null when unmatched or the catalog carries no host." +} - added
Output schema / properties / agents / items / properties / tx_count / descriptionAdded value: +"Transactions this agent run would fetch, over every counterparty listed above." - added
Output schema / properties / agents / items / properties / urlAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The provider's portal address as the catalog holds it, e.g. \"https://members.wework.com/invoices\". `domain` is this address reduced to its host. Null when unmatched or the catalog carries no address. Display only — it labels the portal and never selects it." +} - changed
Output schema / properties / agents / items / requiredPrevious value: -[ - "provider_name", - "provider_id", - "logo_url", - "counterparties", - "tx_count", - "base_total_amount" -]New value: +[ + "provider_name", + "provider_id", + "domain", + "url", + "logo_url", + "counterparties", + "connect_routed_counterparties", + "tx_count", + "base_total_amount" +] - added
Output schema / properties / collect_urlAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The `/collect` entry that hands these portals to the Well browser extension, naming each one by its `provider_id` and naming this workspace as the link's authorization scope — the page starts nothing until the reader is signed in to Well as a member of it. Null when no agent here carries a provider id, so no portal can be addressed. Opening it starts nothing on its own: the user acts on the page." +} - added
Output schema / properties / collect_url_omitsAdded value: +{ + "description": "The portals `collect_url` does NOT name, because one link carries at most 25. Present only when the ceiling left some out. Report those vendors as outside the link — it starts nothing for them.", + "items": { + "additionalProperties": false, + "properties": { + "provider_id": { + "type": "string" + }, + "provider_name": { + "type": "string" + } + }, + "required": [ + "provider_id", + "provider_name" + ], + "type": "object" + }, + "type": "array" +} - changed
Output schema / properties / counts / descriptionPrevious value: -"Totals across every month read."New value: +"The whole window's counts. `agents` counts the distinct portals across it, so it is never the sum of the months' own `agents`. `upload` and `connect` add each month's counterparty rows, so a counterparty missing an invoice in two of the months read counts once per month." - changed
Output schema / properties / counts / properties / agent_tx / descriptionPrevious value: -"Transactions those agents would cover."New value: +"Transactions those agent runs would fetch." - changed
Output schema / properties / counts / properties / agents / descriptionPrevious value: -"Agents that would run — one per distinct provider."New value: +"Portals an agent run is available for — one per distinct provider, the connector-covered ones included." - added
Output schema / properties / counts / properties / connect / descriptionAdded value: +"Counterparties whose suggested route is connecting a service." - added
Output schema / properties / counts / properties / upload / descriptionAdded value: +"Counterparties whose invoice only a manual upload can obtain." - changed
Output schema / properties / months / items / properties / counts / properties / agent_tx / descriptionPrevious value: -"Transactions those agents would cover."New value: +"Transactions those agent runs would fetch." - changed
Output schema / properties / months / items / properties / counts / properties / agents / descriptionPrevious value: -"Agents that would run — one per distinct provider."New value: +"Portals an agent run is available for — one per distinct provider, the connector-covered ones included." - added
Output schema / properties / months / items / properties / counts / properties / connect / descriptionAdded value: +"Counterparties whose suggested route is connecting a service." - added
Output schema / properties / months / items / properties / counts / properties / upload / descriptionAdded value: +"Counterparties whose invoice only a manual upload can obtain."
3 tool updates
- Changed
well_list_workspaces2 fields changed- added
Output schema / properties / session / properties / selected_counterpartiesAdded value: +{ + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "counterparties": { + "items": { + "additionalProperties": false, + "properties": { + "company_id": { + "type": "string" + }, + "matched_connector_service_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The connector that can fetch this counterparty's invoices; null when none matched." + } + }, + "required": [ + "company_id", + "matched_connector_service_id" + ], + "type": "object" + }, + "type": "array" + }, + "workspace_id": { + "description": "The workspace the picked company ids belong to.", + "type": "string" + } + }, + "required": [ + "workspace_id", + "counterparties" + ], + "type": "object" + }, + { + "type": "null" + } + ], + "description": "The counterparties picked on the missing-invoices card; null until a pick is recorded." +} - changed
Output schema / properties / session / requiredPrevious value: -[ - "pinned_workspace_id", - "workspace_queue", - "selected_periods" -]New value: +[ + "pinned_workspace_id", + "workspace_queue", + "selected_periods", + "selected_counterparties" +]
- Changed
well_switch_workspace7 fields changed- changed
Input schema / properties / ack / descriptionPrevious value: -"Acknowledge a flow step: \"connectors\" for the connect-tools step, \"bank\" for the bank step."New value: +"Acknowledge a flow step: \"connectors\" for the connect-tools step, \"bank\" for the bank step. The acknowledgement is scoped to the workspace this call is dispatched to, and a switch to another workspace clears it. An acknowledgement sent for a workspace this connection has switched away from is refused, and the pinned workspace's own acknowledgement of that step stands." - added
Input schema / properties / counterpartiesAdded value: +{ + "description": "The counterparties the user selected, stored as this session's selected_counterparties and scoped to the workspace this call is dispatched to. At most 200 — a longer list is refused, so a select-all keeps to that bound. Copy the ids off the row: company_id, plus matched_connector_service_id when the row carries one. Send the card's months as counterparty_periods in the same call, so the pick applies to those months only; with none named, the months this session already holds bound it. The session holds ONE selection, so this REPLACES the previous one — but only when the call names the pinned workspace: a selection sent for a workspace this connection has switched away from is refused, and the pinned workspace's selection stands.", + "items": { + "additionalProperties": false, + "properties": { + "company_id": { + "description": "The counterparty company's id, as listed by well_list_counterparties or well_list_missing_invoices.", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", + "type": "string" + }, + "matched_connector_service_id": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The matched provider's connector service id, copied from the row's matched_connector_service_id. Omit or pass null when the row matched no provider; never substitute the provider's name." + } + }, + "required": [ + "company_id" + ], + "type": "object" + }, + "maxItems": 200, + "minItems": 1, + "type": "array" +} - added
Input schema / properties / counterparty_periodsAdded value: +{ + "description": "The months the counterparties card listed, which bound the pick sent as counterparties in the same call. Send it only with counterparties; it never becomes this session's selected months, so it cannot overwrite what the user validated on the period card. With none named, the months this session already holds bound the pick.", + "items": { + "additionalProperties": false, + "properties": { + "calendar_month": { + "description": "Calendar month, 1 = January … 12 = December.", + "maximum": 12, + "minimum": 1, + "type": "integer" + }, + "calendar_year": { + "description": "Calendar year, e.g. 2026.", + "maximum": 2100, + "minimum": 2000, + "type": "integer" + } + }, + "required": [ + "calendar_year", + "calendar_month" + ], + "type": "object" + }, + "maxItems": 12, + "minItems": 1, + "type": "array" +} - changed
Input schema / properties / periods / descriptionPrevious value: -"The months the user selected, stored as this session's selected_periods. Later period-scoped reads default to them when called without a period."New value: +"The months the user VALIDATED on the period card, stored as this session's selected_periods. Later period-scoped reads default to them when called without a period. This field is the user's month selection alone — to bound a counterparty pick, send counterparty_periods instead." - changed
Input schema / properties / workspace_ids / descriptionPrevious value: -"Ordered workspace selection: the FIRST entry becomes this connection's pin, the rest the workspace_queue to work through afterwards. Every entry must be authorized for this connection, or the whole call is refused."New value: +"Ordered workspace selection, and the ONLY field that moves the pin: the FIRST entry becomes this connection's pin, and the rest REPLACE the workspace_queue — so a one-entry list empties a queue that still holds workspaces. Every entry must be authorized for this connection, or the whole call is refused. Never send it to name the workspace of a periods, counterparties or ack call: the universal workspace_id already targets those, while a re-pin to the id this connection already holds writes nothing and clears the queue." - changed
Output schema / properties / changed / items / enumPrevious value: -[ - "workspace", - "periods", - "connectors_ack", - "bank_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "connectors_ack", + "bank_ack" +] - added
Output schema / properties / selected_counterpartiesAdded value: +{ + "items": { + "additionalProperties": false, + "properties": { + "company_id": { + "description": "The counterparty company's id, as listed by well_list_counterparties or well_list_missing_invoices.", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", + "type": "string" + }, + "matched_connector_service_id": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The matched provider's connector service id, copied from the row's matched_connector_service_id. Omit or pass null when the row matched no provider; never substitute the provider's name." + } + }, + "required": [ + "company_id" + ], + "type": "object" + }, + "type": "array" +}
- Changed
well_wait_for_selection6 fields changed- changed
Input schema / properties / kind / descriptionPrevious value: -"Which card click to wait for: \"workspace\" (the workspace picker's Use), \"periods\" (the month picker's Validate), \"connect_ack\" / \"bank_ack\" (the connect card's Continue)."New value: +"Which card click to wait for: \"workspace\" (the workspace picker's Use), \"periods\" (the month picker's Validate), \"counterparties\" (the missing-invoices card's Continue), \"connect_ack\" / \"bank_ack\" (the connect card's Continue)." - changed
Input schema / properties / kind / enumPrevious value: -[ - "workspace", - "periods", - "connect_ack", - "bank_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "connect_ack", + "bank_ack" +] - changed
Output schema / properties / kind / enumPrevious value: -[ - "workspace", - "periods", - "connect_ack", - "bank_ack" -]New value: +[ + "workspace", + "periods", + "counterparties", + "connect_ack", + "bank_ack" +] - added
Output schema / properties / selection / properties / counterpartiesAdded value: +{ + "items": { + "additionalProperties": false, + "properties": { + "company_id": { + "type": "string" + }, + "matched_connector_service_id": { + "description": "The matched provider's connector service id; absent when the picked row matched no provider.", + "type": "string" + } + }, + "required": [ + "company_id" + ], + "type": "object" + }, + "type": "array" +} - added
Output schema / properties / selection / properties / periods / descriptionAdded value: +"The picked months on kind \"periods\"; the months the picked counterparties were listed for on kind \"counterparties\" — empty when the pick names none and so applies to every month read." - added
Output schema / properties / selection / properties / workspace_id / descriptionAdded value: +"The pinned workspace on kind \"workspace\"; the workspace the picked counterparties belong to; the workspace the acknowledgement was made in."
2 tool updates
- Changed
well_get_cost_structure2 fields changed- added
Output schema / properties / period_endAdded value: +{ + "description": "Inclusive YYYY-MM-DD end of that window.", + "type": "string" +} - added
Output schema / properties / period_startAdded value: +{ + "description": "Inclusive YYYY-MM-DD start of the window the amounts cover (the latest closed month).", + "type": "string" +}
- Added
well_get_design_tokens
Related MCP Connectors
Connect Claude or Cursor to books, invoices, bills, payroll, and sealed closes.
Connect Xero to AI agents with secure read or write access across multiple organisations.
Connect QuickBooks Online to AI agents with secure read or write access across multiple companies.
Form companies, manage bank accounts, cards, invoices and more — directly from your AI coding tools.
Related MCP Servers
- AlicenseBqualityBmaintenanceEnables AI assistants to query and manage self-hosted accounting data—invoices, balances, and books—through natural language, with read-only tools by default and optional scoped write operations.10MIT
- AlicenseAqualityDmaintenanceEnables Wave invoicing operations including listing invoices, retrieving details, and generating branded PDFs directly from AI assistants.57 npmISC
- AlicenseCqualityCmaintenanceEnables AI assistants to manage invoices, contacts, purchases, journal entries, and other accounting operations via the Fiken API.1006 npm1MIT
- AlicenseNot gradedqualityDmaintenanceConnects AI assistants to QuickBooks Online, enabling management of invoices, customers, expenses, and reports through natural language.MIT
Glama MCP Gateway
Add one secure layer between your agents and this server.